From f2b9a9c2b0a3d7af3b56df9ae09db8a3b2d8506c Mon Sep 17 00:00:00 2001
From: emmauss <emmausssss@gmail.com>
Date: Thu, 6 Feb 2020 11:25:47 +0000
Subject: [PATCH] Render Profiler in GUI (#854)

* move profiler output to gui

* addressed commits, rebased

* removed whitespaces
---
 ARMeilleure/ARMeilleure.csproj                |   9 +
 Ryujinx.Audio/Ryujinx.Audio.csproj            |   4 +-
 Ryujinx.Common/Ryujinx.Common.csproj          |   4 +-
 Ryujinx.Debugger/Debugger.cs                  |  32 +
 .../Profiler}/DumpProfile.cs                  |   2 +-
 .../Profiler}/InternalProfile.cs              |  22 +-
 .../Profiler}/Profile.cs                      |  27 +-
 .../Profiler}/ProfileConfig.cs                |   2 +-
 .../Profiler}/ProfileSorters.cs               |   2 +-
 .../Profiler}/ProfilerConfiguration.cs        |   6 +-
 .../Profiler}/Settings.cs                     |   5 +-
 .../Profiler}/TimingFlag.cs                   |   2 +-
 .../Profiler}/TimingInfo.cs                   |   2 +-
 .../ProfilerConfig.jsonc                      |   0
 Ryujinx.Debugger/Ryujinx.Debugger.csproj      |  42 +
 Ryujinx.Debugger/UI/DebuggerWidget.cs         |  42 +
 Ryujinx.Debugger/UI/DebuggerWidget.glade      |  44 +
 Ryujinx.Debugger/UI/ProfilerWidget.cs         | 801 ++++++++++++++++++
 Ryujinx.Debugger/UI/ProfilerWidget.glade      | 232 +++++
 Ryujinx.Debugger/UI/SkRenderer.cs             |  23 +
 Ryujinx.HLE/HOS/Services/IpcService.cs        |   2 +-
 Ryujinx.HLE/PerformanceStatistics.cs          |   2 +-
 Ryujinx.HLE/Ryujinx.HLE.csproj                |   6 +-
 Ryujinx.LLE/Luea.csproj                       |   4 +-
 Ryujinx.Profiler/ProfilerKeyboardHandler.cs   |  28 -
 Ryujinx.Profiler/Ryujinx.Profiler.csproj      |  39 -
 Ryujinx.Profiler/UI/ProfileButton.cs          | 110 ---
 Ryujinx.Profiler/UI/ProfileWindow.cs          | 773 -----------------
 Ryujinx.Profiler/UI/ProfileWindowBars.cs      |  85 --
 Ryujinx.Profiler/UI/ProfileWindowGraph.cs     | 151 ----
 Ryujinx.Profiler/UI/ProfileWindowManager.cs   |  95 ---
 .../UI/SharpFontHelpers/FontService.cs        | 257 ------
 .../Ryujinx.ShaderTools.csproj                |   4 +-
 .../Ryujinx.Tests.Unicorn.csproj              |   4 +-
 Ryujinx.Tests/Ryujinx.Tests.csproj            |   4 +-
 Ryujinx.sln                                   |  22 +-
 Ryujinx/Program.cs                            |   2 +-
 Ryujinx/Ryujinx.csproj                        |   8 +-
 Ryujinx/Ui/GLScreen.cs                        |  20 -
 Ryujinx/Ui/MainWindow.cs                      |  60 +-
 Ryujinx/Ui/MainWindow.glade                   |  18 +-
 41 files changed, 1358 insertions(+), 1639 deletions(-)
 create mode 100644 Ryujinx.Debugger/Debugger.cs
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/DumpProfile.cs (97%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/InternalProfile.cs (93%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/Profile.cs (88%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/ProfileConfig.cs (99%)
 rename {Ryujinx.Profiler/UI => Ryujinx.Debugger/Profiler}/ProfileSorters.cs (97%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/ProfilerConfiguration.cs (94%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/Settings.cs (86%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/TimingFlag.cs (87%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/TimingInfo.cs (99%)
 rename {Ryujinx.Profiler => Ryujinx.Debugger}/ProfilerConfig.jsonc (100%)
 create mode 100644 Ryujinx.Debugger/Ryujinx.Debugger.csproj
 create mode 100644 Ryujinx.Debugger/UI/DebuggerWidget.cs
 create mode 100644 Ryujinx.Debugger/UI/DebuggerWidget.glade
 create mode 100644 Ryujinx.Debugger/UI/ProfilerWidget.cs
 create mode 100644 Ryujinx.Debugger/UI/ProfilerWidget.glade
 create mode 100644 Ryujinx.Debugger/UI/SkRenderer.cs
 delete mode 100644 Ryujinx.Profiler/ProfilerKeyboardHandler.cs
 delete mode 100644 Ryujinx.Profiler/Ryujinx.Profiler.csproj
 delete mode 100644 Ryujinx.Profiler/UI/ProfileButton.cs
 delete mode 100644 Ryujinx.Profiler/UI/ProfileWindow.cs
 delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowBars.cs
 delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowGraph.cs
 delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowManager.cs
 delete mode 100644 Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs

diff --git a/ARMeilleure/ARMeilleure.csproj b/ARMeilleure/ARMeilleure.csproj
index 4f55243fe..9567838ec 100644
--- a/ARMeilleure/ARMeilleure.csproj
+++ b/ARMeilleure/ARMeilleure.csproj
@@ -13,6 +13,15 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
   <ItemGroup>
     <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
   </ItemGroup>
diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj
index 588b69181..b541043c7 100644
--- a/Ryujinx.Audio/Ryujinx.Audio.csproj
+++ b/Ryujinx.Audio/Ryujinx.Audio.csproj
@@ -12,7 +12,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
@@ -22,7 +22,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj
index 7f6fa3232..43a853a40 100644
--- a/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/Ryujinx.Common/Ryujinx.Common.csproj
@@ -12,7 +12,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
@@ -22,7 +22,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.Debugger/Debugger.cs b/Ryujinx.Debugger/Debugger.cs
new file mode 100644
index 000000000..6dd3354ce
--- /dev/null
+++ b/Ryujinx.Debugger/Debugger.cs
@@ -0,0 +1,32 @@
+using System;
+using Ryujinx.Debugger.UI;
+
+namespace Ryujinx.Debugger
+{
+    public class Debugger : IDisposable
+    {
+        public DebuggerWidget Widget { get; set; }
+
+        public Debugger()
+        {
+            Widget = new DebuggerWidget();
+        }
+
+        public void Enable()
+        {
+            Widget.Enable();
+        }
+
+        public void Disable()
+        {
+            Widget.Disable();
+        }
+
+        public void Dispose()
+        {
+            Disable();
+
+            Widget.Dispose();
+        }
+    }
+}
diff --git a/Ryujinx.Profiler/DumpProfile.cs b/Ryujinx.Debugger/Profiler/DumpProfile.cs
similarity index 97%
rename from Ryujinx.Profiler/DumpProfile.cs
rename to Ryujinx.Debugger/Profiler/DumpProfile.cs
index 62a027615..e73314d4a 100644
--- a/Ryujinx.Profiler/DumpProfile.cs
+++ b/Ryujinx.Debugger/Profiler/DumpProfile.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public static class DumpProfile
     {
diff --git a/Ryujinx.Profiler/InternalProfile.cs b/Ryujinx.Debugger/Profiler/InternalProfile.cs
similarity index 93%
rename from Ryujinx.Profiler/InternalProfile.cs
rename to Ryujinx.Debugger/Profiler/InternalProfile.cs
index 034624442..0bda9e049 100644
--- a/Ryujinx.Profiler/InternalProfile.cs
+++ b/Ryujinx.Debugger/Profiler/InternalProfile.cs
@@ -1,12 +1,12 @@
-using System;
+using Ryujinx.Common;
+using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using Ryujinx.Common;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public class InternalProfile
     {
@@ -26,17 +26,17 @@ namespace Ryujinx.Profiler
 
         // Cleanup thread
         private readonly Thread _cleanupThread;
-        private bool _cleanupRunning;
-        private readonly long _history;
-        private long _preserve;
+        private bool            _cleanupRunning;
+        private readonly long   _history;
+        private long            _preserve;
 
         // Timing flags
         private TimingFlag[] _timingFlags;
-        private long[] _timingFlagAverages;
-        private long[] _timingFlagLast;
-        private long[] _timingFlagLastDelta;
-        private int _timingFlagCount;
-        private int _timingFlagIndex;
+        private long[]       _timingFlagAverages;
+        private long[]       _timingFlagLast;
+        private long[]       _timingFlagLastDelta;
+        private int          _timingFlagCount;
+        private int          _timingFlagIndex;
 
         private int _maxFlags;
 
diff --git a/Ryujinx.Profiler/Profile.cs b/Ryujinx.Debugger/Profiler/Profile.cs
similarity index 88%
rename from Ryujinx.Profiler/Profile.cs
rename to Ryujinx.Debugger/Profiler/Profile.cs
index 4dba6ea54..862aa845d 100644
--- a/Ryujinx.Profiler/Profile.cs
+++ b/Ryujinx.Debugger/Profiler/Profile.cs
@@ -4,19 +4,17 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public static class Profile
     {
         public static float UpdateRate    => _settings.UpdateRate;
         public static long  HistoryLength => _settings.History;
 
-        public static ProfilerKeyboardHandler Controls      => _settings.Controls;
-
         private static InternalProfile  _profileInstance;
         private static ProfilerSettings _settings;
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void Initialize()
         {
             var config = ProfilerConfiguration.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProfilerConfig.jsonc"));
@@ -29,14 +27,13 @@ namespace Ryujinx.Profiler
                 UpdateRate      = (config.UpdateRate <= 0) ? -1 : 1.0f / config.UpdateRate,
                 History         = (long)(config.History * PerformanceCounter.TicksPerSecond),
                 MaxLevel        = config.MaxLevel,
-                Controls        = config.Controls,
                 MaxFlags        = config.MaxFlags,
             };
         }
 
         public static bool ProfilingEnabled()
         {
-#if USE_PROFILING
+#if USE_DEBUGGING
             if (!_settings.Enabled)
                 return false;
 
@@ -49,7 +46,7 @@ namespace Ryujinx.Profiler
 #endif
         }
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void FinishProfiling()
         {
             if (!ProfilingEnabled())
@@ -61,7 +58,7 @@ namespace Ryujinx.Profiler
             _profileInstance.Dispose();
         }
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void FlagTime(TimingFlagType flagType)
         {
             if (!ProfilingEnabled())
@@ -69,7 +66,7 @@ namespace Ryujinx.Profiler
             _profileInstance.FlagTime(flagType);
         }
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void RegisterFlagReceiver(Action<TimingFlag> receiver)
         {
             if (!ProfilingEnabled())
@@ -77,7 +74,7 @@ namespace Ryujinx.Profiler
             _profileInstance.RegisterFlagReceiver(receiver);
         }
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void Begin(ProfileConfig config)
         {
             if (!ProfilingEnabled())
@@ -87,7 +84,7 @@ namespace Ryujinx.Profiler
             _profileInstance.BeginProfile(config);
         }
 
-        [Conditional("USE_PROFILING")]
+        [Conditional("USE_DEBUGGING")]
         public static void End(ProfileConfig config)
         {
             if (!ProfilingEnabled())
@@ -99,7 +96,7 @@ namespace Ryujinx.Profiler
 
         public static string GetSession()
         {
-#if USE_PROFILING
+#if USE_DEBUGGING
             if (!ProfilingEnabled())
                 return null;
             return _profileInstance.GetSession();
@@ -110,7 +107,7 @@ namespace Ryujinx.Profiler
 
         public static List<KeyValuePair<ProfileConfig, TimingInfo>> GetProfilingData()
         {
-#if USE_PROFILING
+#if USE_DEBUGGING
             if (!ProfilingEnabled())
                 return new List<KeyValuePair<ProfileConfig, TimingInfo>>();
             return _profileInstance.GetProfilingData();
@@ -121,7 +118,7 @@ namespace Ryujinx.Profiler
 
         public static TimingFlag[] GetTimingFlags()
         {
-#if USE_PROFILING
+#if USE_DEBUGGING
             if (!ProfilingEnabled())
                 return new TimingFlag[0];
             return _profileInstance.GetTimingFlags();
@@ -132,7 +129,7 @@ namespace Ryujinx.Profiler
 
         public static (long[], long[]) GetTimingAveragesAndLast()
         {
-#if USE_PROFILING
+#if USE_DEBUGGING
             if (!ProfilingEnabled())
                 return (new long[0], new long[0]);
             return _profileInstance.GetTimingAveragesAndLast();
diff --git a/Ryujinx.Profiler/ProfileConfig.cs b/Ryujinx.Debugger/Profiler/ProfileConfig.cs
similarity index 99%
rename from Ryujinx.Profiler/ProfileConfig.cs
rename to Ryujinx.Debugger/Profiler/ProfileConfig.cs
index 4271bd2b8..0ec3e26db 100644
--- a/Ryujinx.Profiler/ProfileConfig.cs
+++ b/Ryujinx.Debugger/Profiler/ProfileConfig.cs
@@ -1,6 +1,6 @@
 using System;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public struct ProfileConfig : IEquatable<ProfileConfig>
     {
diff --git a/Ryujinx.Profiler/UI/ProfileSorters.cs b/Ryujinx.Debugger/Profiler/ProfileSorters.cs
similarity index 97%
rename from Ryujinx.Profiler/UI/ProfileSorters.cs
rename to Ryujinx.Debugger/Profiler/ProfileSorters.cs
index 9f66de224..2b730af58 100644
--- a/Ryujinx.Profiler/UI/ProfileSorters.cs
+++ b/Ryujinx.Debugger/Profiler/ProfileSorters.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace Ryujinx.Profiler.UI
+namespace Ryujinx.Debugger.Profiler
 {
     public static class ProfileSorters
     {
diff --git a/Ryujinx.Profiler/ProfilerConfiguration.cs b/Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs
similarity index 94%
rename from Ryujinx.Profiler/ProfilerConfiguration.cs
rename to Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs
index 4fe616fa9..e0842f2e1 100644
--- a/Ryujinx.Profiler/ProfilerConfiguration.cs
+++ b/Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs
@@ -1,10 +1,10 @@
-using OpenTK.Input;
+using Gdk;
 using System;
 using System.IO;
 using Utf8Json;
 using Utf8Json.Resolvers;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public class ProfilerConfiguration
     {
@@ -15,8 +15,6 @@ namespace Ryujinx.Profiler
         public int    MaxFlags   { get; private set; }
         public float  History    { get; private set; }
 
-        public ProfilerKeyboardHandler Controls { get; private set; }
-
         /// <summary>
         /// Loads a configuration file from disk
         /// </summary>
diff --git a/Ryujinx.Profiler/Settings.cs b/Ryujinx.Debugger/Profiler/Settings.cs
similarity index 86%
rename from Ryujinx.Profiler/Settings.cs
rename to Ryujinx.Debugger/Profiler/Settings.cs
index f0c851b22..52aa0d842 100644
--- a/Ryujinx.Profiler/Settings.cs
+++ b/Ryujinx.Debugger/Profiler/Settings.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public class ProfilerSettings
     {
@@ -13,8 +13,5 @@
         // 19531225 = 5 seconds in ticks on most pc's.
         // It should get set on boot to the time specified in config
         public long History { get; set; } = 19531225;
-
-        // Controls
-        public ProfilerKeyboardHandler Controls;
     }
 }
diff --git a/Ryujinx.Profiler/TimingFlag.cs b/Ryujinx.Debugger/Profiler/TimingFlag.cs
similarity index 87%
rename from Ryujinx.Profiler/TimingFlag.cs
rename to Ryujinx.Debugger/Profiler/TimingFlag.cs
index 0cf55bdf3..8a34ac99f 100644
--- a/Ryujinx.Profiler/TimingFlag.cs
+++ b/Ryujinx.Debugger/Profiler/TimingFlag.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public enum TimingFlagType
     {
diff --git a/Ryujinx.Profiler/TimingInfo.cs b/Ryujinx.Debugger/Profiler/TimingInfo.cs
similarity index 99%
rename from Ryujinx.Profiler/TimingInfo.cs
rename to Ryujinx.Debugger/Profiler/TimingInfo.cs
index 6058ddbd8..90bd63d29 100644
--- a/Ryujinx.Profiler/TimingInfo.cs
+++ b/Ryujinx.Debugger/Profiler/TimingInfo.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace Ryujinx.Profiler
+namespace Ryujinx.Debugger.Profiler
 {
     public struct Timestamp
     {
diff --git a/Ryujinx.Profiler/ProfilerConfig.jsonc b/Ryujinx.Debugger/ProfilerConfig.jsonc
similarity index 100%
rename from Ryujinx.Profiler/ProfilerConfig.jsonc
rename to Ryujinx.Debugger/ProfilerConfig.jsonc
diff --git a/Ryujinx.Debugger/Ryujinx.Debugger.csproj b/Ryujinx.Debugger/Ryujinx.Debugger.csproj
new file mode 100644
index 000000000..a67662cc0
--- /dev/null
+++ b/Ryujinx.Debugger/Ryujinx.Debugger.csproj
@@ -0,0 +1,42 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <Configurations>Debug;Release;Profile Release;Profile Debug</Configurations>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="UI\DebuggerWidget.glade" />
+    <None Remove="UI\ProfilerWidget.glade" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="UI\DebuggerWidget.glade" />
+    <EmbeddedResource Include="UI\ProfilerWidget.glade" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="GtkSharp" Version="3.22.25.56" />
+    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1.1" />
+    <PackageReference Include="SkiaSharp.Views.Gtk3" Version="1.68.1.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="ProfilerConfig.jsonc">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Debugger/UI/DebuggerWidget.cs b/Ryujinx.Debugger/UI/DebuggerWidget.cs
new file mode 100644
index 000000000..b2d8458dc
--- /dev/null
+++ b/Ryujinx.Debugger/UI/DebuggerWidget.cs
@@ -0,0 +1,42 @@
+using Gtk;
+using System;
+using GUI = Gtk.Builder.ObjectAttribute;
+
+namespace Ryujinx.Debugger.UI
+{
+    public class DebuggerWidget : Box
+    {
+        public event EventHandler DebuggerEnabled;
+        public event EventHandler DebuggerDisabled;
+
+        [GUI] Notebook _widgetNotebook;
+
+        public DebuggerWidget() : this(new Builder("Ryujinx.Debugger.UI.DebuggerWidget.glade")) { }
+
+        public DebuggerWidget(Builder builder) : base(builder.GetObject("_debuggerBox").Handle)
+        {
+            builder.Autoconnect(this);
+
+            LoadProfiler();
+        }
+
+        public void LoadProfiler()
+        {
+            ProfilerWidget widget = new ProfilerWidget();
+
+            widget.RegisterParentDebugger(this);
+
+            _widgetNotebook.AppendPage(widget, new Label("Profiler"));
+        }
+
+        public void Enable()
+        {
+            DebuggerEnabled.Invoke(this, null);
+        }
+
+        public void Disable()
+        {
+            DebuggerDisabled.Invoke(this, null);
+        }
+    }
+}
diff --git a/Ryujinx.Debugger/UI/DebuggerWidget.glade b/Ryujinx.Debugger/UI/DebuggerWidget.glade
new file mode 100644
index 000000000..7e6e691de
--- /dev/null
+++ b/Ryujinx.Debugger/UI/DebuggerWidget.glade
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.21.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkBox" id="_debuggerBox">
+    <property name="name">DebuggerBox</property>
+    <property name="width_request">1024</property>
+    <property name="height_request">720</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkNotebook" id="_widgetNotebook">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <child>
+          <placeholder/>
+        </child>
+        <child type="tab">
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child type="tab">
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child type="tab">
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/Ryujinx.Debugger/UI/ProfilerWidget.cs b/Ryujinx.Debugger/UI/ProfilerWidget.cs
new file mode 100644
index 000000000..0dc4b84f4
--- /dev/null
+++ b/Ryujinx.Debugger/UI/ProfilerWidget.cs
@@ -0,0 +1,801 @@
+using Gtk;
+using Ryujinx.Common;
+using Ryujinx.Debugger.Profiler;
+using SkiaSharp;
+using SkiaSharp.Views.Desktop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+using GUI = Gtk.Builder.ObjectAttribute;
+
+namespace Ryujinx.Debugger.UI
+{
+    public class ProfilerWidget : Box
+    {
+        private Thread _profilerThread;
+        private double _prevTime;
+        private bool   _profilerRunning;
+        
+        private TimingFlag[] _timingFlags;
+
+        private bool _initComplete  = false;
+        private bool _redrawPending = true;
+        private bool _doStep        = false;
+
+        // Layout
+        private const int LineHeight         = 16;
+        private const int MinimumColumnWidth = 200;
+        private const int TitleHeight        = 24;
+        private const int TitleFontHeight    = 16;
+        private const int LinePadding        = 2;
+        private const int ColumnSpacing      = 15;
+        private const int FilterHeight       = 24;
+        private const int BottomBarHeight    = FilterHeight + LineHeight;
+
+        // Sorting
+        private List<KeyValuePair<ProfileConfig, TimingInfo>>      _unsortedProfileData;
+        private IComparer<KeyValuePair<ProfileConfig, TimingInfo>> _sortAction = new ProfileSorters.TagAscending();
+
+        // Flag data
+        private long[] _timingFlagsAverages;
+        private long[] _timingFlagsLast;
+
+        // Filtering
+        private string _filterText   = "";
+        private bool   _regexEnabled = false;
+
+        // Scrolling
+        private float _scrollPos = 0;
+
+        // Profile data storage
+        private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
+        private long _captureTime;
+
+        // Graph
+        private SKColor[] _timingFlagColors = new[]
+        {
+            new SKColor(150, 25, 25, 50), // FrameSwap   = 0
+            new SKColor(25, 25, 150, 50), // SystemFrame = 1
+        };
+
+        private const float GraphMoveSpeed = 40000;
+        private const float GraphZoomSpeed = 50;
+
+        private float _graphZoom = 1;
+        private float _graphPosition = 0;
+        private int _rendererHeight => _renderer.AllocatedHeight;
+        private int _rendererWidth  => _renderer.AllocatedWidth;
+
+        // Event management
+        private long            _lastOutputUpdate;
+        private long            _lastOutputDraw;
+        private long            _lastOutputUpdateDuration;
+        private long            _lastOutputDrawDuration;
+        private double          _lastFrameTimeMs;
+        private double          _updateTimer;
+        private bool            _profileUpdated  = false;
+        private readonly object _profileDataLock = new object();
+
+        private SkRenderer _renderer;
+
+        [GUI] ScrolledWindow _scrollview;
+        [GUI] CheckButton    _enableCheckbutton;
+        [GUI] Scrollbar      _outputScrollbar;
+        [GUI] Entry          _filterBox;
+        [GUI] ComboBox       _modeBox;
+        [GUI] CheckButton    _showFlags;
+        [GUI] CheckButton    _showInactive;
+        [GUI] Button         _stepButton;
+        [GUI] CheckButton    _pauseCheckbutton;
+
+        public ProfilerWidget() : this(new Builder("Ryujinx.Debugger.UI.ProfilerWidget.glade")) { }
+
+        public ProfilerWidget(Builder builder) : base(builder.GetObject("_profilerBox").Handle)
+        {
+            builder.Autoconnect(this);
+
+            this.KeyPressEvent += ProfilerWidget_KeyPressEvent;
+
+            this.Expand = true;
+
+            _renderer = new SkRenderer();
+            _renderer.Expand = true;
+
+            _outputScrollbar.ValueChanged += _outputScrollbar_ValueChanged;
+
+            _renderer.DrawGraphs += _renderer_DrawGraphs;
+
+            _filterBox.Changed += _filterBox_Changed;
+
+            _stepButton.Clicked += _stepButton_Clicked;
+
+            _scrollview.Add(_renderer);
+
+            if (Profile.UpdateRate <= 0)
+            {
+                // Perform step regardless of flag type
+                Profile.RegisterFlagReceiver((t) =>
+                {
+                    if (_pauseCheckbutton.Active)
+                    {
+                        _doStep = true;
+                    }
+                });
+            }
+        }
+
+        private void _stepButton_Clicked(object sender, EventArgs e)
+        {
+            if (_pauseCheckbutton.Active)
+            {
+                _doStep = true;
+            }
+
+            _profileUpdated = true;
+        }
+
+        private void _filterBox_Changed(object sender, EventArgs e)
+        {
+            _filterText     = _filterBox.Text;
+            _profileUpdated = true;
+        }
+
+        private void _outputScrollbar_ValueChanged(object sender, EventArgs e)
+        {
+            _scrollPos      = -(float)Math.Max(0, _outputScrollbar.Value);
+            _profileUpdated = true;
+        }
+
+        private void _renderer_DrawGraphs(object sender, EventArgs e)
+        {
+            if (e is SKPaintSurfaceEventArgs se)
+            {
+                Draw(se.Surface.Canvas);
+            }
+        }
+
+        public void RegisterParentDebugger(DebuggerWidget debugger)
+        {
+            debugger.DebuggerEnabled  += Debugger_DebuggerAttached;
+            debugger.DebuggerDisabled += Debugger_DebuggerDettached;
+        }
+
+        private void Debugger_DebuggerDettached(object sender, EventArgs e)
+        {
+            _profilerRunning = false;
+
+            if (_profilerThread != null)
+            {
+                _profilerThread.Join();
+            }
+        }
+
+        private void Debugger_DebuggerAttached(object sender, EventArgs e)
+        {
+            _profilerRunning = false;
+
+            if (_profilerThread != null)
+            {
+                _profilerThread.Join();
+            }
+
+            _profilerRunning = true;
+
+            _profilerThread = new Thread(UpdateLoop)
+            {
+                Name = "Profiler.UpdateThread"
+            };
+            _profilerThread.Start();
+        }
+
+        private void ProfilerWidget_KeyPressEvent(object o, Gtk.KeyPressEventArgs args)
+        {
+            switch (args.Event.Key)
+            {
+                case Gdk.Key.Left:
+                    _graphPosition += (long)(GraphMoveSpeed * _lastFrameTimeMs);
+                    break;
+
+                case Gdk.Key.Right:
+                    _graphPosition = Math.Max(_graphPosition - (long)(GraphMoveSpeed * _lastFrameTimeMs), 0);
+                    break;
+
+                case Gdk.Key.Up:
+                    _graphZoom = MathF.Min(_graphZoom + (float)(GraphZoomSpeed * _lastFrameTimeMs), 100.0f);
+                    break;
+
+                case Gdk.Key.Down:
+                    _graphZoom = MathF.Max(_graphZoom - (float)(GraphZoomSpeed * _lastFrameTimeMs), 1f);
+                    break;
+            }
+            _profileUpdated = true;
+        }
+
+        public void UpdateLoop()
+        {
+            _lastOutputUpdate = PerformanceCounter.ElapsedTicks;
+            _lastOutputDraw   = PerformanceCounter.ElapsedTicks;
+
+            while (_profilerRunning)
+            {
+                _lastOutputUpdate = PerformanceCounter.ElapsedTicks;
+                int timeToSleepMs = (_pauseCheckbutton.Active || !_enableCheckbutton.Active) ? 33 : 1;
+
+                if (Profile.ProfilingEnabled() && _enableCheckbutton.Active)
+                {
+                    double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond;
+
+                    Update(time - _prevTime);
+
+                    _lastOutputUpdateDuration = PerformanceCounter.ElapsedTicks - _lastOutputUpdate;
+                    _prevTime = time;
+
+                    Gdk.Threads.AddIdle(1000, ()=> 
+                    {
+                        _renderer.QueueDraw();
+
+                        return true;
+                    });
+                }
+
+                Thread.Sleep(timeToSleepMs);
+            }
+        }
+
+        public void Update(double frameTime)
+        {
+            _lastFrameTimeMs = frameTime;
+
+            // Get timing data if enough time has passed
+            _updateTimer += frameTime;
+
+            if (_doStep || ((Profile.UpdateRate > 0) && (!_pauseCheckbutton.Active && (_updateTimer > Profile.UpdateRate))))
+            {
+                _updateTimer    = 0;
+                _captureTime    = PerformanceCounter.ElapsedTicks;
+                _timingFlags    = Profile.GetTimingFlags();
+                _doStep         = false;
+                _profileUpdated = true;
+
+                _unsortedProfileData = Profile.GetProfilingData();
+
+                (_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast();
+            }
+
+            // Filtering
+            if (_profileUpdated)
+            {
+                lock (_profileDataLock)
+                {
+                    _sortedProfileData = _showInactive.Active ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
+
+                    if (_sortAction != null)
+                    {
+                        _sortedProfileData.Sort(_sortAction);
+                    }
+
+                    if (_regexEnabled)
+                    {
+                        try
+                        {
+                            Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
+                            if (_filterText != "")
+                            {
+                                _sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
+                            }
+                        }
+                        catch (ArgumentException argException)
+                        {
+                            // Skip filtering for invalid regex
+                        }
+                    }
+                    else
+                    {
+                        // Regular filtering
+                        _sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
+                    }
+                }
+
+                _profileUpdated = false;
+                _redrawPending  = true;
+                _initComplete   = true;
+            }
+        }
+
+        private string GetTimeString(long timestamp)
+        {
+            float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond;
+
+            return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms";
+        }
+
+        private void FilterBackspace()
+        {
+            if (_filterText.Length <= 1)
+            {
+                _filterText = "";
+            }
+            else
+            {
+                _filterText = _filterText.Remove(_filterText.Length - 1, 1);
+            }
+        }
+
+        private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line)
+        {
+            return offset + lineHeight + padding + ((lineHeight + padding) * line) - ((centre) ? padding : 0);
+        }
+
+        public void Draw(SKCanvas canvas)
+        {
+            _lastOutputDraw = PerformanceCounter.ElapsedTicks;
+            if (!Visible                   ||
+                !_initComplete             ||
+                !_enableCheckbutton.Active ||
+                !_redrawPending)
+            {
+                return;
+            }
+
+            float viewTop    = TitleHeight + 5;
+            float viewBottom = _rendererHeight - FilterHeight - LineHeight;
+
+            float columnWidth;
+            float maxColumnWidth = MinimumColumnWidth;
+            float yOffset        = _scrollPos + viewTop;
+            float xOffset        = 10;
+            float timingWidth;
+
+            float contentHeight = GetLineY(0, LineHeight, LinePadding, false, _sortedProfileData.Count - 1);
+
+            _outputScrollbar.Adjustment.Upper    = contentHeight;
+            _outputScrollbar.Adjustment.Lower    = 0;
+            _outputScrollbar.Adjustment.PageSize = viewBottom - viewTop;
+
+
+            SKPaint textFont = new SKPaint()
+            {
+                Color    = SKColors.White,
+                TextSize = LineHeight
+            };
+
+            SKPaint titleFont = new SKPaint()
+            {
+                Color    = SKColors.White,
+                TextSize = TitleFontHeight
+            };
+
+            SKPaint evenItemBackground = new SKPaint()
+            {
+                Color = SKColors.Gray
+            };
+
+            canvas.Save();
+            canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
+
+            for (int i = 1; i < _sortedProfileData.Count; i += 2)
+            {
+                float top    = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1);
+                float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i);
+
+                canvas.DrawRect(new SKRect(0, top, _rendererWidth, bottom), evenItemBackground);
+            }
+
+            lock (_profileDataLock)
+            {
+                // Display category
+
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+
+                    if (entry.Key.Category == null)
+                    {
+                        continue;
+                    }
+
+                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
+
+                    canvas.DrawText(entry.Key.Category, new SKPoint(xOffset, y), textFont);
+
+                    columnWidth = textFont.MeasureText(entry.Key.Category);
+
+                    if (columnWidth > maxColumnWidth)
+                    {
+                        maxColumnWidth = columnWidth;
+                    }
+                }
+
+                canvas.Restore();
+                canvas.DrawText("Category", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
+
+                columnWidth = titleFont.MeasureText("Category");
+
+                if (columnWidth > maxColumnWidth)
+                {
+                    maxColumnWidth = columnWidth;
+                }
+
+                xOffset += maxColumnWidth + ColumnSpacing;
+
+                canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
+
+                // Display session group
+                maxColumnWidth = MinimumColumnWidth;
+
+                canvas.Save();
+                canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
+
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+
+                    if (entry.Key.SessionGroup == null)
+                    {
+                        continue;
+                    }
+
+                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
+
+                    canvas.DrawText(entry.Key.SessionGroup, new SKPoint(xOffset, y), textFont);
+
+                    columnWidth = textFont.MeasureText(entry.Key.SessionGroup);
+
+                    if (columnWidth > maxColumnWidth)
+                    {
+                        maxColumnWidth = columnWidth;
+                    }
+                }
+
+                canvas.Restore();
+                canvas.DrawText("Group", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
+
+                columnWidth = titleFont.MeasureText("Group");
+
+                if (columnWidth > maxColumnWidth)
+                {
+                    maxColumnWidth = columnWidth;
+                }
+
+                xOffset += maxColumnWidth + ColumnSpacing;
+
+                canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
+
+                // Display session item
+                maxColumnWidth = MinimumColumnWidth;
+
+                canvas.Save();
+                canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
+
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+
+                    if (entry.Key.SessionItem == null)
+                    {
+                        continue;
+                    }
+
+                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
+
+                    canvas.DrawText(entry.Key.SessionGroup, new SKPoint(xOffset, y), textFont);
+
+                    columnWidth = textFont.MeasureText(entry.Key.SessionItem);
+
+                    if (columnWidth > maxColumnWidth)
+                    {
+                        maxColumnWidth = columnWidth;
+                    }
+                }
+
+                canvas.Restore();
+                canvas.DrawText("Item", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
+
+                columnWidth = titleFont.MeasureText("Item");
+
+                if (columnWidth > maxColumnWidth)
+                {
+                    maxColumnWidth = columnWidth;
+                }
+
+                xOffset += maxColumnWidth + ColumnSpacing;
+
+                timingWidth = _rendererWidth - xOffset - 370;
+
+                canvas.Save();
+                canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
+                canvas.DrawLine(new SKPoint(xOffset, 0), new SKPoint(xOffset, _rendererHeight), textFont);
+
+                int mode = _modeBox.Active;
+
+                canvas.Save();
+                canvas.ClipRect(new SKRect(xOffset, yOffset,xOffset + timingWidth,yOffset + contentHeight), 
+                                            SKClipOperation.Intersect);
+
+                switch (mode)
+                {
+                    case 0: 
+                        DrawGraph(xOffset, yOffset, timingWidth, canvas);
+                        break;
+                    case 1: 
+                        DrawBars(xOffset, yOffset, timingWidth, canvas);
+
+                        canvas.DrawText("Blue: Instant,  Green: Avg,  Red: Total", 
+                                        new SKPoint(xOffset, _rendererHeight - TitleFontHeight), titleFont);
+                        break;
+                }
+
+                canvas.Restore();
+                canvas.DrawLine(new SKPoint(xOffset + timingWidth, 0), new SKPoint(xOffset + timingWidth, _rendererHeight), textFont);
+
+                xOffset = _rendererWidth - 360;
+
+                // Display timestamps
+                long totalInstant = 0;
+                long totalAverage = 0;
+                long totalTime    = 0;
+                long totalCount   = 0;
+
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+
+                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
+
+                    canvas.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", new SKPoint(xOffset, y), textFont);
+                    canvas.DrawText(GetTimeString(entry.Value.AverageTime), new SKPoint(150 + xOffset, y), textFont);
+                    canvas.DrawText(GetTimeString(entry.Value.TotalTime), new SKPoint(260 + xOffset, y), textFont);
+
+                    totalInstant += entry.Value.Instant;
+                    totalAverage += entry.Value.AverageTime;
+                    totalTime    += entry.Value.TotalTime;
+                    totalCount   += entry.Value.InstantCount;
+                }
+
+                canvas.Restore();
+                canvas.DrawLine(new SKPoint(0, viewTop), new SKPoint(_rendererWidth, viewTop), titleFont);
+
+                float yHeight = 0 + TitleFontHeight;
+
+                canvas.DrawText("Instant (Count)", new SKPoint(xOffset, yHeight), titleFont);
+                canvas.DrawText("Average", new SKPoint(150 + xOffset, yHeight), titleFont);
+                canvas.DrawText("Total (ms)", new SKPoint(260 + xOffset, yHeight), titleFont);
+
+                // Totals
+                yHeight = _rendererHeight - FilterHeight + 3;
+
+                int textHeight = LineHeight - 2;
+
+                SKPaint detailFont = new SKPaint()
+                {
+                    Color = new SKColor(100, 100, 255, 255),
+                    TextSize = textHeight
+                };
+
+                canvas.DrawLine(new SkiaSharp.SKPoint(0, viewBottom), new SkiaSharp.SKPoint(_rendererWidth,viewBottom), textFont);
+
+                string hostTimeString = $"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " +
+                                        $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})";
+
+                canvas.DrawText(hostTimeString, new SKPoint(5, yHeight), detailFont);
+
+                float tempWidth = detailFont.MeasureText(hostTimeString);
+
+                detailFont.Color = SKColors.Red;
+
+                string gameTimeString = $"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " +
+                                        $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})";
+
+                canvas.DrawText(gameTimeString, new SKPoint(15 + tempWidth, yHeight), detailFont);
+
+                tempWidth += detailFont.MeasureText(gameTimeString);
+
+                detailFont.Color = SKColors.White;
+
+                canvas.DrawText($"Profiler: Update {GetTimeString(_lastOutputUpdateDuration)} Draw {GetTimeString(_lastOutputDrawDuration)}", 
+                    new SKPoint(20 + tempWidth, yHeight), detailFont);
+
+                detailFont.Color = SKColors.White;
+
+                canvas.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", new SKPoint(xOffset, yHeight), detailFont);
+                canvas.DrawText(GetTimeString(totalAverage), new SKPoint(150 + xOffset, yHeight), detailFont);
+                canvas.DrawText(GetTimeString(totalTime), new SKPoint(260 + xOffset, yHeight), detailFont);
+
+                _lastOutputDrawDuration = PerformanceCounter.ElapsedTicks - _lastOutputDraw;
+            }
+        }
+
+        private void DrawGraph(float xOffset, float yOffset, float width, SKCanvas canvas)
+        {
+            if (_sortedProfileData.Count != 0)
+            {
+                int   left, right;
+                float top,  bottom;
+
+                float  graphRight         = xOffset + width;
+                float  barHeight          = (LineHeight - LinePadding);
+                long   history            = Profile.HistoryLength;
+                double timeWidthTicks     = history / (double)_graphZoom;
+                long   graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond);
+                long   ticksPerPixel      = (long)(timeWidthTicks / width);
+
+                // Reset start point if out of bounds
+                if (timeWidthTicks + graphPositionTicks > history)
+                {
+                    graphPositionTicks = history - (long)timeWidthTicks;
+                    _graphPosition     = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond;
+                }
+
+                graphPositionTicks = _captureTime - graphPositionTicks;
+
+                // Draw timing flags
+                if (_showFlags.Active)
+                {
+                    TimingFlagType prevType = TimingFlagType.Count;
+
+                    SKPaint timingPaint = new SKPaint
+                    {
+                        Color = _timingFlagColors.First()
+                    };
+
+                    foreach (TimingFlag timingFlag in _timingFlags)
+                    {
+                        if (prevType != timingFlag.FlagType)
+                        {
+                            prevType = timingFlag.FlagType;
+                            timingPaint.Color = _timingFlagColors[(int)prevType];
+                        }
+
+                        int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width);
+
+                        if (x > xOffset)
+                        {
+                            canvas.DrawLine(new SKPoint(x, yOffset), new SKPoint(x, _rendererHeight), timingPaint);
+                        }
+                    }
+                }
+
+                SKPaint barPaint = new SKPaint()
+                {
+                    Color = SKColors.Green,
+                };
+
+                // Draw bars
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+                    long furthest = 0;
+
+                    bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
+                    top    = bottom + barHeight;
+
+                    // Skip rendering out of bounds bars
+                    if (top < 0 || bottom > _rendererHeight)
+                    {
+                        continue;
+                    }
+
+                    barPaint.Color = SKColors.Green;
+
+                    foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
+                    {
+                        // Skip drawing multiple timestamps on same pixel
+                        if (timestamp.EndTime < furthest)
+                        {
+                            continue;
+                        }
+
+                        furthest = timestamp.EndTime + ticksPerPixel;
+
+                        left  = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width);
+                        right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime)   / timeWidthTicks) * width);
+
+                        left = (int)Math.Max(xOffset +1, left);
+
+                        // Make sure width is at least 1px
+                        right = Math.Max(left + 1, right);
+
+                        canvas.DrawRect(new SKRect(left, top, right, bottom), barPaint);
+                    }
+
+                    // Currently capturing timestamp
+                    barPaint.Color = SKColors.Red;
+
+                    long entryBegin = entry.Value.BeginTime;
+
+                    if (entryBegin != -1)
+                    {
+                        left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width);
+
+                        // Make sure width is at least 1px
+                        left = Math.Min(left - 1, (int)graphRight);
+
+                        left = (int)Math.Max(xOffset + 1, left);
+
+                        canvas.DrawRect(new SKRect(left, top, graphRight, bottom), barPaint);
+                    }
+                }
+
+                string label = $"-{MathF.Round(_graphPosition, 2)} ms";
+
+                SKPaint labelPaint = new SKPaint()
+                {
+                    Color    = SKColors.White,
+                    TextSize = LineHeight
+                };
+
+                float labelWidth = labelPaint.MeasureText(label);
+
+                canvas.DrawText(label,new SKPoint(graphRight - labelWidth - LinePadding, FilterHeight + LinePadding) , labelPaint);
+
+                canvas.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms",
+                    new SKPoint(xOffset + LinePadding, FilterHeight + LinePadding), labelPaint);
+            }
+        }
+
+        private void DrawBars(float xOffset, float yOffset, float width, SKCanvas canvas)
+        {
+            if (_sortedProfileData.Count != 0)
+            {
+                long maxAverage = 0;
+                long maxTotal   = 0;
+                long maxInstant = 0;
+
+                float barHeight = (LineHeight - LinePadding) / 3.0f;
+
+                // Get max values
+                foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
+                {
+                    maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
+                    maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
+                    maxTotal   = Math.Max(maxTotal, kvp.Value.TotalTime);
+                }
+
+                SKPaint barPaint = new SKPaint()
+                {
+                    Color = SKColors.Blue
+                };
+
+                for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
+                {
+                    KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
+                    // Instant
+                    barPaint.Color = SKColors.Blue;
+
+                    float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
+                    float top    = bottom + barHeight;
+                    float right  = (float)entry.Value.Instant / maxInstant * width + xOffset;
+
+                    // Skip rendering out of bounds bars
+                    if (top < 0 || bottom > _rendererHeight)
+                    {
+                        continue;
+                    }
+
+                    canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
+
+                    // Average
+                    barPaint.Color = SKColors.Green;
+
+                    top    += barHeight;
+                    bottom += barHeight;
+                    right   = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
+
+                    canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
+
+                    // Total
+                    barPaint.Color = SKColors.Red;
+
+                    top    += barHeight;
+                    bottom += barHeight;
+                    right   = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
+
+                    canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Debugger/UI/ProfilerWidget.glade b/Ryujinx.Debugger/UI/ProfilerWidget.glade
new file mode 100644
index 000000000..00dd4f704
--- /dev/null
+++ b/Ryujinx.Debugger/UI/ProfilerWidget.glade
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.21.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkListStore" id="viewMode">
+    <columns>
+      <!-- column-name mode -->
+      <column type="gint"/>
+      <!-- column-name label -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">0</col>
+        <col id="1" translatable="yes">Graph</col>
+      </row>
+      <row>
+        <col id="0">1</col>
+        <col id="1" translatable="yes">Bars</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkBox" id="_profilerBox">
+    <property name="name">ProfilerBox</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_left">5</property>
+    <property name="margin_right">5</property>
+    <property name="margin_top">5</property>
+    <property name="margin_bottom">5</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">10</property>
+    <child>
+      <object class="GtkCheckButton" id="_enableCheckbutton">
+        <property name="label" translatable="yes">Enable Profiler</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">False</property>
+        <property name="draw_indicator">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkScrolledWindow" id="_scrollview">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="vscrollbar_policy">never</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrollbar" id="_outputScrollbar">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkCheckButton" id="_showInactive">
+            <property name="label" translatable="yes">Show Inactive</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="_showFlags">
+            <property name="label" translatable="yes">Show Flags</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="_pauseCheckbutton">
+            <property name="label" translatable="yes">Paused</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">View Mode: </property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="_modeBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="model">viewMode</property>
+                <property name="active">0</property>
+                <child>
+                  <object class="GtkCellRendererText" id="modeTextRenderer"/>
+                  <attributes>
+                    <attribute name="text">1</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Filter: </property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="_filterBox">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="_stepButton">
+            <property name="label" translatable="yes">Step</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">5</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/Ryujinx.Debugger/UI/SkRenderer.cs b/Ryujinx.Debugger/UI/SkRenderer.cs
new file mode 100644
index 000000000..a95e4542c
--- /dev/null
+++ b/Ryujinx.Debugger/UI/SkRenderer.cs
@@ -0,0 +1,23 @@
+using SkiaSharp;
+using SkiaSharp.Views.Gtk;
+using System;
+
+namespace Ryujinx.Debugger.UI
+{
+    public class SkRenderer : SKDrawingArea
+    {
+        public event EventHandler DrawGraphs;
+
+        public SkRenderer()
+        {
+            this.PaintSurface += SkRenderer_PaintSurface;
+        }
+
+        private void SkRenderer_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e)
+        {
+            e.Surface.Canvas.Clear(SKColors.Black);
+
+            DrawGraphs.Invoke(this, e);
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs
index e95386835..67df453e5 100644
--- a/Ryujinx.HLE/HOS/Services/IpcService.cs
+++ b/Ryujinx.HLE/HOS/Services/IpcService.cs
@@ -6,7 +6,7 @@ using Ryujinx.HLE.HOS.Kernel.Ipc;
 using System;
 using System.Collections.Generic;
 using System.IO;
-using Ryujinx.Profiler;
+using Ryujinx.Debugger.Profiler;
 using System.Reflection;
 using System.Linq;
 
diff --git a/Ryujinx.HLE/PerformanceStatistics.cs b/Ryujinx.HLE/PerformanceStatistics.cs
index 896ab67b0..5abf2628e 100644
--- a/Ryujinx.HLE/PerformanceStatistics.cs
+++ b/Ryujinx.HLE/PerformanceStatistics.cs
@@ -1,4 +1,4 @@
-using Ryujinx.Profiler;
+using Ryujinx.Debugger.Profiler;
 using System.Diagnostics;
 using System.Timers;
 
diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj
index ac4b82240..e9540ab87 100644
--- a/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -12,7 +12,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
@@ -22,7 +22,7 @@
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
@@ -44,7 +44,7 @@
   <ItemGroup>
     <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
     <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
-    <ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
+    <ProjectReference Include="..\Ryujinx.Debugger\Ryujinx.Debugger.csproj" />
     <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
diff --git a/Ryujinx.LLE/Luea.csproj b/Ryujinx.LLE/Luea.csproj
index 7eb546d8f..0184d1be7 100644
--- a/Ryujinx.LLE/Luea.csproj
+++ b/Ryujinx.LLE/Luea.csproj
@@ -8,12 +8,12 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.Profiler/ProfilerKeyboardHandler.cs b/Ryujinx.Profiler/ProfilerKeyboardHandler.cs
deleted file mode 100644
index e6207e892..000000000
--- a/Ryujinx.Profiler/ProfilerKeyboardHandler.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using OpenTK.Input;
-
-namespace Ryujinx.Profiler
-{
-    public struct ProfilerButtons
-    {
-        public Key ToggleProfiler;
-    }
-
-    public class ProfilerKeyboardHandler
-    {
-        public ProfilerButtons Buttons;
-
-        private KeyboardState _prevKeyboard;
-
-        public ProfilerKeyboardHandler(ProfilerButtons buttons)
-        {
-            Buttons = buttons;
-        }
-
-        public bool TogglePressed(KeyboardState keyboard) => !keyboard[Buttons.ToggleProfiler] && _prevKeyboard[Buttons.ToggleProfiler];
-
-        public void SetPrevKeyboardState(KeyboardState keyboard)
-        {
-            _prevKeyboard = keyboard;
-        }
-    }
-}
diff --git a/Ryujinx.Profiler/Ryujinx.Profiler.csproj b/Ryujinx.Profiler/Ryujinx.Profiler.csproj
deleted file mode 100644
index 0e089ccf5..000000000
--- a/Ryujinx.Profiler/Ryujinx.Profiler.csproj
+++ /dev/null
@@ -1,39 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>netcoreapp3.0</TargetFramework>
-    <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <Configurations>Debug;Release;Profile Debug;Profile Release</Configurations>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
-    <DefineConstants>TRACE</DefineConstants>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
-    <Optimize>false</Optimize>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
-    <Optimize>true</Optimize>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
-    <PackageReference Include="SharpFontCore" Version="0.1.1" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
-  </ItemGroup>
-
-  <ItemGroup Condition="'$(Configuration)' == 'Profile Debug' Or '$(Configuration)' == 'Profile Release'">
-    <None Update="ProfilerConfig.jsonc">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
-  </ItemGroup>
-
-</Project>
diff --git a/Ryujinx.Profiler/UI/ProfileButton.cs b/Ryujinx.Profiler/UI/ProfileButton.cs
deleted file mode 100644
index 7e2ae7288..000000000
--- a/Ryujinx.Profiler/UI/ProfileButton.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using System;
-using OpenTK;
-using OpenTK.Graphics.OpenGL;
-using Ryujinx.Profiler.UI.SharpFontHelpers;
-
-namespace Ryujinx.Profiler.UI
-{
-    public class ProfileButton
-    {
-        // Store font service
-        private FontService _fontService;
-
-        // Layout information
-        private int _left, _right;
-        private int _bottom, _top;
-        private int _height;
-        private int _padding;
-
-        // Label information
-        private int    _labelX, _labelY;
-        private string _label;
-
-        // Misc
-        private Action _clicked;
-        private bool   _visible;
-
-        public ProfileButton(FontService fontService, Action clicked)
-            : this(fontService, clicked, 0, 0, 0, 0, 0)
-        {
-            _visible = false;
-        }
-
-        public ProfileButton(FontService fontService, Action clicked, int x, int y, int padding, int height, int width)
-            : this(fontService, "", clicked, x, y, padding, height, width)
-        {
-            _visible = false;
-        }
-
-        public ProfileButton(FontService fontService, string label, Action clicked, int x, int y, int padding, int height, int width = -1)
-        {
-            _fontService = fontService;
-            _clicked     = clicked;
-
-            UpdateSize(label, x, y, padding, height, width);
-        }
-
-        public int UpdateSize(string label, int x, int y, int padding, int height, int width = -1)
-        {
-            _visible = true;
-            _label   = label;
-
-            if (width == -1)
-            {
-                // Dummy draw to measure size
-                width = (int)_fontService.DrawText(label, 0, 0, height, false);
-            }
-
-            UpdateSize(x, y, padding, width, height);
-
-            return _right - _left;
-        }
-
-        public void UpdateSize(int x, int y, int padding, int width, int height)
-        {
-            _height = height;
-            _left   = x;
-            _bottom = y;
-            _labelX = x + padding / 2;
-            _labelY = y + padding / 2;
-            _top    = y + height + padding;
-            _right  = x + width + padding;
-        }
-
-        public void Draw()
-        {
-            if (!_visible)
-            {
-                return;
-            }
-
-            // Draw backing rectangle
-            GL.Begin(PrimitiveType.Triangles);
-            GL.Color3(Color.Black);
-            GL.Vertex2(_left,  _bottom);
-            GL.Vertex2(_left,  _top);
-            GL.Vertex2(_right, _top);
-
-            GL.Vertex2(_right, _top);
-            GL.Vertex2(_right, _bottom);
-            GL.Vertex2(_left,  _bottom);
-            GL.End();
-
-            // Use font service to draw label
-            _fontService.DrawText(_label, _labelX, _labelY, _height);
-        }
-
-        public bool ProcessClick(int x, int y)
-        {
-            // If button contains x, y
-            if (x > _left   && x < _right &&
-                y > _bottom && y < _top)
-            {
-                _clicked();
-                return true;
-            }
-
-            return false;
-        }
-    }
-}
diff --git a/Ryujinx.Profiler/UI/ProfileWindow.cs b/Ryujinx.Profiler/UI/ProfileWindow.cs
deleted file mode 100644
index 1db70bc79..000000000
--- a/Ryujinx.Profiler/UI/ProfileWindow.cs
+++ /dev/null
@@ -1,773 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text.RegularExpressions;
-using OpenTK;
-using OpenTK.Graphics;
-using OpenTK.Graphics.OpenGL;
-using OpenTK.Input;
-using Ryujinx.Common;
-using Ryujinx.Profiler.UI.SharpFontHelpers;
-
-namespace Ryujinx.Profiler.UI
-{
-    public partial class ProfileWindow : GameWindow
-    {
-        // List all buttons for index in button array
-        private enum ButtonIndex
-        {
-            TagTitle          = 0,
-            InstantTitle      = 1,
-            AverageTitle      = 2,
-            TotalTitle        = 3,
-            FilterBar         = 4,
-            ShowHideInactive  = 5,
-            Pause             = 6,
-            ChangeDisplay     = 7,
-
-            // Don't automatically draw after here
-            ToggleFlags       = 8,
-            Step              = 9,
-
-            // Update this when new buttons are added.
-            // These are indexes to the enum list
-            Autodraw = 8,
-            Count    = 10,
-        }
-
-        // Font service
-        private FontService _fontService;
-
-        // UI variables
-        private ProfileButton[] _buttons;
-
-        private bool _initComplete    = false;
-        private bool _visible         = true;
-        private bool _visibleChanged  = true;
-        private bool _viewportUpdated = true;
-        private bool _redrawPending   = true;
-        private bool _displayGraph    = true;
-        private bool _displayFlags    = true;
-        private bool _showInactive    = true;
-        private bool _paused          = false;
-        private bool _doStep          = false;
-
-        // Layout
-        private const int LineHeight      = 16;
-        private const int TitleHeight     = 24;
-        private const int TitleFontHeight = 16;
-        private const int LinePadding     = 2;
-        private const int ColumnSpacing   = 15;
-        private const int FilterHeight    = 24;
-        private const int BottomBarHeight = FilterHeight + LineHeight;
-
-        // Sorting
-        private List<KeyValuePair<ProfileConfig, TimingInfo>> _unsortedProfileData;
-        private IComparer<KeyValuePair<ProfileConfig, TimingInfo>> _sortAction = new ProfileSorters.TagAscending();
-
-        // Flag data
-        private long[] _timingFlagsAverages;
-        private long[] _timingFlagsLast;
-
-        // Filtering
-        private string _filterText = "";
-        private bool _regexEnabled = false;
-
-        // Scrolling
-        private float _scrollPos = 0;
-        private float _minScroll = 0;
-        private float _maxScroll = 0;
-
-        // Profile data storage
-        private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
-        private long _captureTime;
-
-        // Input
-        private bool _backspaceDown       = false;
-        private bool _prevBackspaceDown   = false;
-        private double _backspaceDownTime = 0;
-
-        // F35 used as no key
-        private Key _graphControlKey = Key.F35;
-
-        // Event management
-        private double _updateTimer;
-        private double _processEventTimer;
-        private bool   _profileUpdated           = false;
-        private readonly object _profileDataLock = new object();
-
-        public ProfileWindow()
-                               // Graphics mode enables 2xAA
-            : base(1280, 720, new GraphicsMode(new ColorFormat(8, 8, 8, 8), 1, 1, 2))
-        {
-            Title    = "Profiler";
-            Location = new Point(DisplayDevice.Default.Width  - 1280,
-                                (DisplayDevice.Default.Height - 720) - 50);
-
-            if (Profile.UpdateRate <= 0)
-            {
-                // Perform step regardless of flag type
-                Profile.RegisterFlagReceiver((t) =>
-                {
-                    if (!_paused)
-                    {
-                        _doStep = true;
-                    }
-                });
-            }
-
-            // Large number to force an update on first update
-            _updateTimer = 0xFFFF;
-
-            Init();
-
-            // Release context for render thread
-            Context.MakeCurrent(null);
-        }
-        
-        public void ToggleVisible()
-        {
-            _visible = !_visible;
-            _visibleChanged = true;
-        }
-
-        private void SetSort(IComparer<KeyValuePair<ProfileConfig, TimingInfo>> filter)
-        {
-            _sortAction = filter;
-            _profileUpdated = true;
-        }
-
-#region OnLoad
-        /// <summary>
-        /// Setup OpenGL and load resources
-        /// </summary>
-        public void Init()
-        {
-            GL.ClearColor(Color.Black);
-            _fontService = new FontService();
-            _fontService.InitializeTextures();
-            _fontService.UpdateScreenHeight(Height);
-
-            _buttons = new ProfileButton[(int)ButtonIndex.Count];
-            _buttons[(int)ButtonIndex.TagTitle]      = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TagAscending()));
-            _buttons[(int)ButtonIndex.InstantTitle]  = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.InstantAscending()));
-            _buttons[(int)ButtonIndex.AverageTitle]  = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.AverageAscending()));
-            _buttons[(int)ButtonIndex.TotalTitle]    = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TotalAscending()));
-            _buttons[(int)ButtonIndex.Step]          = new ProfileButton(_fontService, () => _doStep = true);
-            _buttons[(int)ButtonIndex.FilterBar]     = new ProfileButton(_fontService, () =>
-            {
-                _profileUpdated = true;
-                _regexEnabled = !_regexEnabled;
-            });
-
-            _buttons[(int)ButtonIndex.ShowHideInactive] = new ProfileButton(_fontService, () =>
-            {
-                _profileUpdated = true;
-                _showInactive = !_showInactive;
-            });
-
-            _buttons[(int)ButtonIndex.Pause] = new ProfileButton(_fontService, () =>
-            {
-                _profileUpdated = true;
-                _paused = !_paused;
-            });
-
-            _buttons[(int)ButtonIndex.ToggleFlags] = new ProfileButton(_fontService, () =>
-            {
-                _displayFlags = !_displayFlags;
-                _redrawPending = true;
-            });
-
-            _buttons[(int)ButtonIndex.ChangeDisplay] = new ProfileButton(_fontService, () =>
-            {
-                _displayGraph = !_displayGraph;
-                _redrawPending = true;
-            });
-
-            Visible = _visible;
-        }
-#endregion
-
-#region OnResize
-        /// <summary>
-        /// Respond to resize events
-        /// </summary>
-        /// <param name="e">Contains information on the new GameWindow size.</param>
-        /// <remarks>There is no need to call the base implementation.</remarks>
-        protected override void OnResize(EventArgs e)
-        {
-            _viewportUpdated = true;
-        }
-#endregion
-
-#region OnClose
-        /// <summary>
-        /// Intercept close event and hide instead
-        /// </summary>
-        protected override void OnClosing(CancelEventArgs e)
-        {
-            // Hide window
-            _visible        = false;
-            _visibleChanged = true;
-
-            // Cancel close
-            e.Cancel = true;
-
-            base.OnClosing(e);
-        }
-#endregion
-
-#region OnUpdateFrame
-        /// <summary>
-        /// Profile Update Loop
-        /// </summary>
-        /// <param name="e">Contains timing information.</param>
-        /// <remarks>There is no need to call the base implementation.</remarks>
-        public void Update(FrameEventArgs e)
-        {
-            if (_visibleChanged)
-            {
-                Visible = _visible;
-                _visibleChanged = false;
-            }
-
-            // Backspace handling
-            if (_backspaceDown)
-            {
-                if (!_prevBackspaceDown)
-                {
-                    _backspaceDownTime = 0;
-                    FilterBackspace();
-                }
-                else
-                {
-                    _backspaceDownTime += e.Time;
-                    if (_backspaceDownTime > 0.3)
-                    {
-                        _backspaceDownTime -= 0.05;
-                        FilterBackspace();
-                    }
-                }
-            }
-            _prevBackspaceDown = _backspaceDown;
-
-            // Get timing data if enough time has passed
-            _updateTimer += e.Time;
-            if (_doStep || ((Profile.UpdateRate > 0) && (!_paused && (_updateTimer > Profile.UpdateRate))))
-            {
-                _updateTimer    = 0;
-                _captureTime    = PerformanceCounter.ElapsedTicks;
-                _timingFlags    = Profile.GetTimingFlags();
-                _doStep         = false;
-                _profileUpdated = true;
-
-                _unsortedProfileData                     = Profile.GetProfilingData();
-                (_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast();
-                
-            }
-            
-            // Filtering
-            if (_profileUpdated)
-            {
-                lock (_profileDataLock)
-                {
-                    _sortedProfileData = _showInactive ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
-
-                    if (_sortAction != null)
-                    {
-                        _sortedProfileData.Sort(_sortAction);
-                    }
-
-                    if (_regexEnabled)
-                    {
-                        try
-                        {
-                            Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
-                            if (_filterText != "")
-                            {
-                                _sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
-                            }
-                        }
-                        catch (ArgumentException argException)
-                        {
-                            // Skip filtering for invalid regex
-                        }
-                    }
-                    else
-                    {
-                        // Regular filtering
-                        _sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
-                    }
-                }
-
-                _profileUpdated = false;
-                _redrawPending  = true;
-                _initComplete   = true;
-            }
-
-            // Check for events 20 times a second
-            _processEventTimer += e.Time;
-            if (_processEventTimer > 0.05)
-            {
-                ProcessEvents();
-
-                if (_graphControlKey != Key.F35)
-                {
-                    switch (_graphControlKey)
-                    {
-                        case Key.Left:
-                            _graphPosition += (long) (GraphMoveSpeed * e.Time);
-                            break;
-
-                        case Key.Right:
-                            _graphPosition = Math.Max(_graphPosition - (long) (GraphMoveSpeed * e.Time), 0);
-                            break;
-
-                        case Key.Up:
-                            _graphZoom = MathF.Min(_graphZoom + (float) (GraphZoomSpeed * e.Time), 100.0f);
-                            break;
-
-                        case Key.Down:
-                            _graphZoom = MathF.Max(_graphZoom - (float) (GraphZoomSpeed * e.Time), 1f);
-                            break;
-                    }
-
-                    _redrawPending = true;
-                }
-
-                _processEventTimer = 0;
-            }
-        }
-#endregion
-
-#region OnRenderFrame
-        /// <summary>
-        /// Profile Render Loop
-        /// </summary>
-        /// <remarks>There is no need to call the base implementation.</remarks>
-        public void Draw()
-        {
-            if (!_visible || !_initComplete)
-            {
-                return;
-            }
-            
-            // Update viewport
-            if (_viewportUpdated)
-            {
-                GL.Viewport(0, 0, Width, Height);
-
-                GL.MatrixMode(MatrixMode.Projection);
-                GL.LoadIdentity();
-                GL.Ortho(0, Width, 0, Height, 0.0, 4.0);
-
-                _fontService.UpdateScreenHeight(Height);
-
-                _viewportUpdated = false;
-                _redrawPending   = true;
-            }
-
-            if (!_redrawPending)
-            {
-                return;
-            }
-
-            // Frame setup
-            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
-            GL.ClearColor(Color.Black);
-
-            _fontService.fontColor = Color.White;
-            int verticalIndex   = 0;
-
-            float width;
-            float maxWidth = 0;
-            float yOffset  = _scrollPos - TitleHeight;
-            float xOffset  = 10;
-            float timingDataLeft;
-            float timingWidth;
-
-            // Background lines to make reading easier
-            #region Background Lines
-            GL.Enable(EnableCap.ScissorTest);
-            GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight);
-            GL.Begin(PrimitiveType.Triangles);
-            GL.Color3(0.2f, 0.2f, 0.2f);
-            for (int i = 0; i < _sortedProfileData.Count; i += 2)
-            {
-                float top    = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1);
-                float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i);
-
-                // Skip rendering out of bounds bars
-                if (top < 0 || bottom > Height)
-                    continue;
-
-                GL.Vertex2(0, bottom);
-                GL.Vertex2(0, top);
-                GL.Vertex2(Width, top);
-
-                GL.Vertex2(Width, top);
-                GL.Vertex2(Width, bottom);
-                GL.Vertex2(0, bottom);
-            }
-            GL.End();
-            _maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1);
-#endregion
-            
-            lock (_profileDataLock)
-            {
-// Display category
-#region Category
-                verticalIndex = 0;
-                foreach (var entry in _sortedProfileData)
-                {
-                    if (entry.Key.Category == null)
-                    {
-                        verticalIndex++;
-                        continue;
-                    }
-
-                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
-                    width   = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight);
-
-                    if (width > maxWidth)
-                    {
-                        maxWidth = width;
-                    }
-                }
-                GL.Disable(EnableCap.ScissorTest);
-
-                width = _fontService.DrawText("Category", xOffset, Height - TitleFontHeight, TitleFontHeight);
-                if (width > maxWidth)
-                    maxWidth = width;
-
-                xOffset += maxWidth + ColumnSpacing;
-#endregion
-
-// Display session group
-#region Session Group
-                maxWidth      = 0;
-                verticalIndex = 0;
-
-                GL.Enable(EnableCap.ScissorTest);
-                foreach (var entry in _sortedProfileData)
-                {
-                    if (entry.Key.SessionGroup == null)
-                    {
-                        verticalIndex++;
-                        continue;
-                    }
-
-                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
-                    width   = _fontService.DrawText(entry.Key.SessionGroup, xOffset, y, LineHeight);
-
-                    if (width > maxWidth)
-                    {
-                        maxWidth = width;
-                    }
-                }
-                GL.Disable(EnableCap.ScissorTest);
-
-                width = _fontService.DrawText("Group", xOffset, Height - TitleFontHeight, TitleFontHeight);
-                if (width > maxWidth)
-                    maxWidth = width;
-
-                xOffset += maxWidth + ColumnSpacing;
-#endregion
-
-// Display session item
-#region Session Item
-                maxWidth      = 0;
-                verticalIndex = 0;
-                GL.Enable(EnableCap.ScissorTest);
-                foreach (var entry in _sortedProfileData)
-                {
-                    if (entry.Key.SessionItem == null)
-                    {
-                        verticalIndex++;
-                        continue;
-                    }
-
-                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
-                    width   = _fontService.DrawText(entry.Key.SessionItem, xOffset, y, LineHeight);
-
-                    if (width > maxWidth)
-                    {
-                        maxWidth = width;
-                    }
-                }
-                GL.Disable(EnableCap.ScissorTest);
-
-                width = _fontService.DrawText("Item", xOffset, Height - TitleFontHeight, TitleFontHeight);
-                if (width > maxWidth)
-                    maxWidth = width;
-
-                xOffset += maxWidth + ColumnSpacing;
-                _buttons[(int)ButtonIndex.TagTitle].UpdateSize(0, Height - TitleFontHeight, 0, (int)xOffset, TitleFontHeight);
-#endregion
-
-                // Timing data
-                timingWidth    = Width - xOffset - 370;
-                timingDataLeft = xOffset;
-
-                GL.Scissor((int)xOffset, BottomBarHeight, (int)timingWidth, Height - TitleHeight - BottomBarHeight);
-
-                if (_displayGraph)
-                {
-                    DrawGraph(xOffset, yOffset, timingWidth);
-                }
-                else
-                {
-                    DrawBars(xOffset, yOffset, timingWidth);
-                }
-
-                GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight);
-
-                if (!_displayGraph)
-                {
-                    _fontService.DrawText("Blue: Instant,  Green: Avg,  Red: Total", xOffset, Height - TitleFontHeight, TitleFontHeight);
-                }
-
-                xOffset = Width - 360;
-
-// Display timestamps
-#region Timestamps
-                verticalIndex     = 0;
-                long totalInstant = 0;
-                long totalAverage = 0;
-                long totalTime    = 0;
-                long totalCount   = 0;
-
-                GL.Enable(EnableCap.ScissorTest);
-                foreach (var entry in _sortedProfileData)
-                {
-                    float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
-
-                    _fontService.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", xOffset, y, LineHeight);
-
-                    _fontService.DrawText(GetTimeString(entry.Value.AverageTime), 150 + xOffset, y, LineHeight);
-
-                    _fontService.DrawText(GetTimeString(entry.Value.TotalTime), 260 + xOffset, y, LineHeight);
-
-                    totalInstant += entry.Value.Instant;
-                    totalAverage += entry.Value.AverageTime;
-                    totalTime    += entry.Value.TotalTime;
-                    totalCount   += entry.Value.InstantCount;
-                }
-                GL.Disable(EnableCap.ScissorTest);
-
-                float yHeight = Height - TitleFontHeight;
-
-                _fontService.DrawText("Instant (Count)", xOffset, yHeight, TitleFontHeight);
-                _buttons[(int)ButtonIndex.InstantTitle].UpdateSize((int)xOffset, (int)yHeight, 0, 130, TitleFontHeight);
-
-                _fontService.DrawText("Average", 150 + xOffset, yHeight, TitleFontHeight);
-                _buttons[(int)ButtonIndex.AverageTitle].UpdateSize((int)(150 + xOffset), (int)yHeight, 0, 130, TitleFontHeight);
-
-                _fontService.DrawText("Total (ms)", 260 + xOffset, yHeight, TitleFontHeight);
-                _buttons[(int)ButtonIndex.TotalTitle].UpdateSize((int)(260 + xOffset), (int)yHeight, 0, Width, TitleFontHeight);
-
-                // Totals
-                yHeight = FilterHeight + 3;
-                int textHeight = LineHeight - 2;
-
-                _fontService.fontColor = new Color(100, 100, 255, 255);
-                float tempWidth = _fontService.DrawText($"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " +
-                                                            $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})", 5, yHeight, textHeight);
-
-                _fontService.fontColor = Color.Red;
-                _fontService.DrawText($"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " +
-                                          $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})", 15 + tempWidth, yHeight, textHeight);
-                _fontService.fontColor = Color.White;
-                
-
-                _fontService.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", xOffset,       yHeight, textHeight);
-                _fontService.DrawText(GetTimeString(totalAverage),                     150 + xOffset, yHeight, textHeight);
-                _fontService.DrawText(GetTimeString(totalTime),                        260 + xOffset, yHeight, textHeight);
-#endregion
-            }
-
-#region Bottom bar
-            // Show/Hide Inactive
-            float widthShowHideButton = _buttons[(int)ButtonIndex.ShowHideInactive].UpdateSize($"{(_showInactive ? "Hide" : "Show")} Inactive", 5, 5, 4, 16);
-
-            // Play/Pause
-            float widthPlayPauseButton = _buttons[(int)ButtonIndex.Pause].UpdateSize(_paused ? "Play" : "Pause", 15 + (int)widthShowHideButton, 5, 4, 16) + widthShowHideButton;
-
-            // Step
-            float widthStepButton = widthPlayPauseButton;
-
-            if (_paused)
-            {
-                widthStepButton += _buttons[(int)ButtonIndex.Step].UpdateSize("Step", (int)(25 + widthPlayPauseButton), 5, 4, 16) + 10;
-                _buttons[(int)ButtonIndex.Step].Draw();
-            }
-
-            // Change display
-            float widthChangeDisplay = _buttons[(int)ButtonIndex.ChangeDisplay].UpdateSize($"View: {(_displayGraph ? "Graph" : "Bars")}", 25 + (int)widthStepButton, 5, 4, 16) + widthStepButton;
-
-            width = widthChangeDisplay;
-
-            if (_displayGraph)
-            {
-                width += _buttons[(int) ButtonIndex.ToggleFlags].UpdateSize($"{(_displayFlags ? "Hide" : "Show")} Flags", 35 + (int)widthChangeDisplay, 5, 4, 16) + 10;
-                _buttons[(int)ButtonIndex.ToggleFlags].Draw();
-            }
-
-            // Filter bar
-            _fontService.DrawText($"{(_regexEnabled ? "Regex " : "Filter")}: {_filterText}", 35 + width, 7, 16);
-            _buttons[(int)ButtonIndex.FilterBar].UpdateSize((int)(45 + width), 0, 0, Width, FilterHeight);
-#endregion
-
-            // Draw buttons
-            for (int i = 0; i < (int)ButtonIndex.Autodraw; i++)
-            {
-                _buttons[i].Draw();
-            }
-            
-// Dividing lines
-#region Dividing lines
-            GL.Color3(Color.White);
-            GL.Begin(PrimitiveType.Lines);
-            // Top divider
-            GL.Vertex2(0, Height -TitleHeight);
-            GL.Vertex2(Width, Height - TitleHeight);
-
-            // Bottom divider
-            GL.Vertex2(0,     FilterHeight);
-            GL.Vertex2(Width, FilterHeight);
-
-            GL.Vertex2(0,     BottomBarHeight);
-            GL.Vertex2(Width, BottomBarHeight);
-
-            // Bottom vertical dividers
-            GL.Vertex2(widthShowHideButton + 10, 0);
-            GL.Vertex2(widthShowHideButton + 10, FilterHeight);
-
-            GL.Vertex2(widthPlayPauseButton + 20, 0);
-            GL.Vertex2(widthPlayPauseButton + 20, FilterHeight);
-
-            if (_paused)
-            {
-                GL.Vertex2(widthStepButton + 20, 0);
-                GL.Vertex2(widthStepButton + 20, FilterHeight);
-            }
-
-            if (_displayGraph)
-            {
-                GL.Vertex2(widthChangeDisplay + 30, 0);
-                GL.Vertex2(widthChangeDisplay + 30, FilterHeight);
-            }
-
-            GL.Vertex2(width + 30, 0);
-            GL.Vertex2(width + 30, FilterHeight);
-
-            // Column dividers
-            float timingDataTop = Height - TitleHeight;
-
-            GL.Vertex2(timingDataLeft, FilterHeight);
-            GL.Vertex2(timingDataLeft, timingDataTop);
-            
-            GL.Vertex2(timingWidth + timingDataLeft, FilterHeight);
-            GL.Vertex2(timingWidth + timingDataLeft, timingDataTop);
-            GL.End();
-#endregion
-
-            _redrawPending = false;
-            SwapBuffers();
-        }
-#endregion
-
-        private string GetTimeString(long timestamp)
-        {
-            float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond;
-            return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms";
-        }
-
-        private void FilterBackspace()
-        {
-            if (_filterText.Length <= 1)
-            {
-                _filterText = "";
-            }
-            else
-            {
-                _filterText = _filterText.Remove(_filterText.Length - 1, 1);
-            }
-        }
-
-        private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line)
-        {
-            return Height + offset - lineHeight - padding - ((lineHeight + padding) * line) + ((centre) ? padding : 0);
-        }
-
-        protected override void OnKeyPress(KeyPressEventArgs e)
-        {
-            _filterText += e.KeyChar;
-            _profileUpdated = true;
-        }
-
-        protected override void OnKeyDown(KeyboardKeyEventArgs e)
-        {
-            switch (e.Key)
-            {
-                case Key.BackSpace:
-                    _profileUpdated = _backspaceDown = true;
-                    return;
-
-                case Key.Left:
-                case Key.Right:
-                case Key.Up:
-                case Key.Down:
-                    _graphControlKey = e.Key;
-                    return;
-        }
-            base.OnKeyUp(e);
-        }
-
-        protected override void OnKeyUp(KeyboardKeyEventArgs e)
-        {
-            // Can't go into switch as value isn't constant
-            if (e.Key == Profile.Controls.Buttons.ToggleProfiler)
-            {
-                ToggleVisible();
-                return;
-            }
-
-            switch (e.Key)
-            {
-                case Key.BackSpace:
-                    _backspaceDown = false;
-                    return;
-
-                case Key.Left:
-                case Key.Right:
-                case Key.Up:
-                case Key.Down:
-                    _graphControlKey = Key.F35;
-                    return;
-            }
-            base.OnKeyUp(e);
-        }
-
-        protected override void OnMouseUp(MouseButtonEventArgs e)
-        {
-            foreach (ProfileButton button in _buttons)
-            {
-                if (button.ProcessClick(e.X, Height - e.Y))
-                    return;
-            }
-        }
-
-        protected override void OnMouseWheel(MouseWheelEventArgs e)
-        {
-            _scrollPos += e.Delta * -30;
-            if (_scrollPos < _minScroll)
-                _scrollPos = _minScroll;
-            if (_scrollPos > _maxScroll)
-                _scrollPos = _maxScroll;
-
-            _redrawPending = true;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Profiler/UI/ProfileWindowBars.cs b/Ryujinx.Profiler/UI/ProfileWindowBars.cs
deleted file mode 100644
index ab5b4fd13..000000000
--- a/Ryujinx.Profiler/UI/ProfileWindowBars.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Collections.Generic;
-using OpenTK;
-using OpenTK.Graphics.OpenGL;
-
-namespace Ryujinx.Profiler.UI
-{
-    public partial class ProfileWindow
-    {
-        private void DrawBars(float xOffset, float yOffset, float width)
-        {
-            if (_sortedProfileData.Count != 0)
-            {
-                long maxAverage;
-                long maxTotal;
-
-                int verticalIndex = 0;
-                float barHeight   = (LineHeight - LinePadding) / 3.0f;
-
-                // Get max values
-                long maxInstant = maxAverage = maxTotal = 0;
-                foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
-                {
-                    maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
-                    maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
-                    maxTotal   = Math.Max(maxTotal,   kvp.Value.TotalTime);
-                }
-
-                GL.Enable(EnableCap.ScissorTest);
-                GL.Begin(PrimitiveType.Triangles);
-                foreach (var entry in _sortedProfileData)
-                {
-                    // Instant
-                    GL.Color3(Color.Blue);
-                    float bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
-                    float top    = bottom + barHeight;
-                    float right  = (float)entry.Value.Instant / maxInstant * width + xOffset;
-
-                    // Skip rendering out of bounds bars
-                    if (top < 0 || bottom > Height)
-                        continue;
-
-                    GL.Vertex2(xOffset, bottom);
-                    GL.Vertex2(xOffset, top);
-                    GL.Vertex2(right,   top);
-
-                    GL.Vertex2(right,   top);
-                    GL.Vertex2(right,   bottom);
-                    GL.Vertex2(xOffset, bottom);
-
-                    // Average
-                    GL.Color3(Color.Green);
-                    top    += barHeight;
-                    bottom += barHeight;
-                    right   = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
-
-                    GL.Vertex2(xOffset, bottom);
-                    GL.Vertex2(xOffset, top);
-                    GL.Vertex2(right, top);
-
-                    GL.Vertex2(right, top);
-                    GL.Vertex2(right, bottom);
-                    GL.Vertex2(xOffset, bottom);
-
-                    // Total
-                    GL.Color3(Color.Red);
-                    top    += barHeight;
-                    bottom += barHeight;
-                    right   = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
-
-                    GL.Vertex2(xOffset, bottom);
-                    GL.Vertex2(xOffset, top);
-                    GL.Vertex2(right, top);
-
-                    GL.Vertex2(right, top);
-                    GL.Vertex2(right, bottom);
-                    GL.Vertex2(xOffset, bottom);
-                }
-
-                GL.End();
-                GL.Disable(EnableCap.ScissorTest);
-            }
-        }
-    }
-}
diff --git a/Ryujinx.Profiler/UI/ProfileWindowGraph.cs b/Ryujinx.Profiler/UI/ProfileWindowGraph.cs
deleted file mode 100644
index 6a4a52a99..000000000
--- a/Ryujinx.Profiler/UI/ProfileWindowGraph.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using System;
-using OpenTK;
-using OpenTK.Graphics.OpenGL;
-using Ryujinx.Common;
-
-namespace Ryujinx.Profiler.UI
-{
-    public partial class ProfileWindow
-    {
-        // Color index equal to timing flag type as int
-        private Color[] _timingFlagColors = new[]
-        {
-            new Color(150, 25, 25, 50), // FrameSwap   = 0
-            new Color(25, 25, 150, 50), // SystemFrame = 1
-        };
-
-        private TimingFlag[] _timingFlags;
-
-        private const float GraphMoveSpeed = 40000;
-        private const float GraphZoomSpeed = 50;
-
-        private float _graphZoom      = 1;
-        private float _graphPosition  = 0;
-
-        private void DrawGraph(float xOffset, float yOffset, float width)
-        {
-            if (_sortedProfileData.Count != 0)
-            {
-                int   left, right;
-                float top, bottom;
-
-                int    verticalIndex      = 0;
-                float  graphRight         = xOffset + width;
-                float  barHeight          = (LineHeight - LinePadding);
-                long   history            = Profile.HistoryLength;
-                double timeWidthTicks     = history / (double)_graphZoom;
-                long   graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond);
-                long   ticksPerPixel      = (long)(timeWidthTicks / width);
-
-                // Reset start point if out of bounds
-                if (timeWidthTicks + graphPositionTicks > history)
-                {
-                    graphPositionTicks = history - (long)timeWidthTicks;
-                    _graphPosition     = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond;
-                }
-
-                graphPositionTicks = _captureTime - graphPositionTicks;
-
-                GL.Enable(EnableCap.ScissorTest);
-
-                // Draw timing flags
-                if (_displayFlags)
-                {
-                    TimingFlagType prevType = TimingFlagType.Count;
-
-                    GL.Enable(EnableCap.Blend);
-                    GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
-
-                    GL.Begin(PrimitiveType.Lines);
-                    foreach (TimingFlag timingFlag in _timingFlags)
-                    {
-                        if (prevType != timingFlag.FlagType)
-                        {
-                            prevType = timingFlag.FlagType;
-                            GL.Color4(_timingFlagColors[(int)prevType]);
-                        }
-
-                        int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width);
-                        GL.Vertex2(x, 0);
-                        GL.Vertex2(x, Height);
-                    }
-                    GL.End();
-                    GL.Disable(EnableCap.Blend);
-                }
-
-                // Draw bars
-                GL.Begin(PrimitiveType.Triangles);
-                foreach (var entry in _sortedProfileData)
-                {
-                    long furthest = 0;
-
-                    bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
-                    top    = bottom + barHeight;
-
-                    // Skip rendering out of bounds bars
-                    if (top < 0 || bottom > Height)
-                    {
-                        verticalIndex++;
-                        continue;
-                    }
-
-
-                    GL.Color3(Color.Green);
-                    foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
-                    {
-                        // Skip drawing multiple timestamps on same pixel
-                        if (timestamp.EndTime < furthest)
-                            continue;
-                        furthest = timestamp.EndTime + ticksPerPixel;
-
-                        left  = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width);
-                        right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime)   / timeWidthTicks) * width);
-
-                        // Make sure width is at least 1px
-                        right = Math.Max(left + 1, right);
-
-                        GL.Vertex2(left,  bottom);
-                        GL.Vertex2(left,  top);
-                        GL.Vertex2(right, top);
-
-                        GL.Vertex2(right, top);
-                        GL.Vertex2(right, bottom);
-                        GL.Vertex2(left,  bottom);
-                    }
-
-                    // Currently capturing timestamp
-                    GL.Color3(Color.Red);
-                    long entryBegin = entry.Value.BeginTime;
-                    if (entryBegin != -1)
-                    {
-                        left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width);
-
-                        // Make sure width is at least 1px
-                        left = Math.Min(left - 1, (int)graphRight);
-
-                        GL.Vertex2(left,       bottom);
-                        GL.Vertex2(left,       top);
-                        GL.Vertex2(graphRight, top);
-
-                        GL.Vertex2(graphRight, top);
-                        GL.Vertex2(graphRight, bottom);
-                        GL.Vertex2(left,       bottom);
-                    }
-
-                    verticalIndex++;
-                }
-
-                GL.End();
-                GL.Disable(EnableCap.ScissorTest);
-
-                string label = $"-{MathF.Round(_graphPosition, 2)} ms";
-
-                // Dummy draw for measure
-                float labelWidth = _fontService.DrawText(label, 0, 0, LineHeight, false);
-                _fontService.DrawText(label, graphRight - labelWidth - LinePadding, FilterHeight + LinePadding, LineHeight);
-                
-                _fontService.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms", xOffset + LinePadding, FilterHeight + LinePadding, LineHeight);
-            }
-        }
-    }
-}
diff --git a/Ryujinx.Profiler/UI/ProfileWindowManager.cs b/Ryujinx.Profiler/UI/ProfileWindowManager.cs
deleted file mode 100644
index 136030293..000000000
--- a/Ryujinx.Profiler/UI/ProfileWindowManager.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System.Threading;
-using OpenTK;
-using OpenTK.Input;
-using Ryujinx.Common;
-
-namespace Ryujinx.Profiler.UI
-{
-    public class ProfileWindowManager
-    {
-        private ProfileWindow _window;
-        private Thread _profileThread;
-        private Thread _renderThread;
-        private bool _profilerRunning;
-
-        // Timing
-        private double _prevTime;
-
-        public ProfileWindowManager()
-        {
-            if (Profile.ProfilingEnabled())
-            {
-                _profilerRunning = true;
-                _prevTime        = 0;
-                _profileThread   = new Thread(ProfileLoop)
-                {
-                    Name = "Profiler.ProfileThread"
-                };
-                _profileThread.Start();
-            }
-        }
-
-        public void ToggleVisible()
-        {
-            if (Profile.ProfilingEnabled())
-            {
-                _window.ToggleVisible();
-            }
-        }
-
-        public void Close()
-        {
-            if (_window != null)
-            {
-                _profilerRunning = false;
-                _window.Close();
-                _window.Dispose();
-            }
-
-            _window = null;
-        }
-
-        public void UpdateKeyInput(KeyboardState keyboard)
-        {
-            if (Profile.Controls.TogglePressed(keyboard))
-            {
-                ToggleVisible();
-            }
-            Profile.Controls.SetPrevKeyboardState(keyboard);
-        }
-
-        private void ProfileLoop()
-        {
-            using (_window = new ProfileWindow())
-            {
-                // Create thread for render loop
-                _renderThread = new Thread(RenderLoop)
-                {
-                    Name = "Profiler.RenderThread"
-                };
-                _renderThread.Start();
-
-                while (_profilerRunning)
-                {
-                    double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond;
-                    _window.Update(new FrameEventArgs(time - _prevTime));
-                    _prevTime = time;
-
-                    // Sleep to be less taxing, update usually does very little
-                    Thread.Sleep(1);
-                }
-            }
-        }
-
-        private void RenderLoop()
-        {
-            _window.Context.MakeCurrent(_window.WindowInfo);
-
-            while (_profilerRunning)
-            {
-                _window.Draw();
-                Thread.Sleep(1);
-            }
-        }
-    }
-}
diff --git a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs
deleted file mode 100644
index 32846977e..000000000
--- a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs
+++ /dev/null
@@ -1,257 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-using OpenTK;
-using OpenTK.Graphics.OpenGL;
-using SharpFont;
-
-namespace Ryujinx.Profiler.UI.SharpFontHelpers
-{
-    public class FontService
-    {
-        private struct CharacterInfo
-        {
-            public float Left;
-            public float Right;
-            public float Top;
-            public float Bottom;
-
-            public int Width;
-            public float Height;
-
-            public float AspectRatio;
-
-            public float BearingX;
-            public float BearingY;
-            public float Advance;
-        }
-
-        private const int SheetWidth  = 1024;
-        private const int SheetHeight = 512;
-        private int ScreenWidth, ScreenHeight;
-        private int CharacterTextureSheet;
-        private CharacterInfo[] characters;
-
-        public Color fontColor { get; set; } = Color.Black;
-
-        private string GetFontPath()
-        {
-            string fontFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
-
-            // Only uses Arial, add more fonts here if wanted
-            string path = Path.Combine(fontFolder, "arial.ttf");
-            if (File.Exists(path))
-            {
-                return path;
-            }
-
-            throw new Exception($"Profiler exception. Required font Courier New or Arial not installed to {fontFolder}");
-        }
-
-        public void InitializeTextures()
-        {
-            // Create and init some vars
-            uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight];
-            int x;
-            int y;
-            int lineOffset;
-            int maxHeight;
-
-            x = y = lineOffset = maxHeight = 0;
-            characters = new CharacterInfo[94];
-
-            // Get font
-            var font = new FontFace(File.OpenRead(GetFontPath()));
-
-            // Update raw data for each character
-            for (int i = 0; i < 94; i++)
-            {
-                var surface = RenderSurface((char)(i + 33), font, out float xBearing, out float yBearing, out float advance);
-
-                characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset);
-                characters[i].BearingX = xBearing;
-                characters[i].BearingY = yBearing;
-                characters[i].Advance  = advance;
-
-                if (maxHeight < characters[i].Height)
-                    maxHeight = (int)characters[i].Height;
-            }
-
-            // Fix height for characters shorter than line height
-            for (int i = 0; i < 94; i++)
-            {
-                characters[i].BearingX   /= characters[i].Width;
-                characters[i].BearingY   /= maxHeight;
-                characters[i].Advance    /= characters[i].Width;
-                characters[i].Height     /= maxHeight;
-                characters[i].AspectRatio = (float)characters[i].Width / maxHeight;
-            }
-
-            // Convert raw data into texture
-            CharacterTextureSheet = GL.GenTexture();
-            GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
-
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,     (int)TextureWrapMode.Clamp);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,     (int)TextureWrapMode.Clamp);
-            
-            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, SheetWidth, SheetHeight, 0, PixelFormat.Rgba, PixelType.UnsignedInt8888, rawCharacterSheet);
-
-            GL.BindTexture(TextureTarget.Texture2D, 0);
-        }
-
-        public void UpdateScreenHeight(int height)
-        {
-            ScreenHeight = height;
-        }
-
-        public float DrawText(string text, float x, float y, float height, bool draw = true)
-        {
-            float originalX = x;
-
-            // Skip out of bounds draw
-            if (y < height * -2 || y > ScreenHeight + height * 2)
-            {
-                draw = false;
-            }
-
-            if (draw)
-            {
-                // Use font map texture
-                GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
-
-                // Enable blending and textures
-                GL.Enable(EnableCap.Texture2D);
-                GL.Enable(EnableCap.Blend);
-                GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
-
-                // Draw all characters
-                GL.Begin(PrimitiveType.Triangles);
-                GL.Color4(fontColor);
-            }
-
-            for (int i = 0; i < text.Length; i++)
-            {
-                if (text[i] == ' ')
-                {
-                    x += height / 4;
-                    continue;
-                }
-
-                CharacterInfo charInfo = characters[text[i] - 33];
-                float width = (charInfo.AspectRatio * height);
-                x += (charInfo.BearingX * charInfo.AspectRatio) * width;
-                float right = x + width;
-                if (draw)
-                {
-                    DrawChar(charInfo, x, right, y + height * (charInfo.Height - charInfo.BearingY), y - height * charInfo.BearingY);
-                }
-                x = right + charInfo.Advance * charInfo.AspectRatio + 1;
-            }
-
-            if (draw)
-            {
-                GL.End();
-
-                // Cleanup for caller
-                GL.BindTexture(TextureTarget.Texture2D, 0);
-                GL.Disable(EnableCap.Texture2D);
-                GL.Disable(EnableCap.Blend);
-            }
-
-            // Return width of rendered text
-            return x - originalX;
-        }
-
-        private void DrawChar(CharacterInfo charInfo, float left, float right, float top, float bottom)
-        {
-            GL.TexCoord2(charInfo.Left, charInfo.Bottom);  GL.Vertex2(left, bottom);
-            GL.TexCoord2(charInfo.Left, charInfo.Top);     GL.Vertex2(left, top);
-            GL.TexCoord2(charInfo.Right, charInfo.Top);    GL.Vertex2(right, top);
-
-            GL.TexCoord2(charInfo.Right, charInfo.Top);    GL.Vertex2(right, top);
-            GL.TexCoord2(charInfo.Right, charInfo.Bottom); GL.Vertex2(right, bottom);
-            GL.TexCoord2(charInfo.Left, charInfo.Bottom);  GL.Vertex2(left, bottom);
-        }
-
-        public unsafe Surface RenderSurface(char c, FontFace font, out float xBearing, out float yBearing, out float advance)
-        {
-            var glyph = font.GetGlyph(c, 64);
-            xBearing  = glyph.HorizontalMetrics.Bearing.X;
-            yBearing  = glyph.RenderHeight - glyph.HorizontalMetrics.Bearing.Y;
-            advance   = glyph.HorizontalMetrics.Advance;
-
-            var surface = new Surface
-            {
-                Bits   = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight),
-                Width  = glyph.RenderWidth,
-                Height = glyph.RenderHeight,
-                Pitch  = glyph.RenderWidth
-            };
-
-            var stuff = (byte*)surface.Bits;
-            for (int i = 0; i < surface.Width * surface.Height; i++)
-                *stuff++ = 0;
-
-            glyph.RenderTo(surface);
-
-            return surface;
-        }
-
-        private CharacterInfo UpdateTexture(Surface surface, ref uint[] rawCharMap, ref int posX, ref int posY, ref int lineOffset)
-        {
-            int width   = surface.Width;
-            int height  = surface.Height;
-            int len     = width * height;
-            byte[] data = new byte[len];
-
-            // Get character bitmap
-            Marshal.Copy(surface.Bits, data, 0, len);
-
-            // Find a slot
-            if (posX + width > SheetWidth)
-            {
-                posX       = 0;
-                posY      += lineOffset;
-                lineOffset = 0;
-            }
-
-            // Update lineOffset
-            if (lineOffset < height)
-            {
-                lineOffset = height + 1;
-            }
-
-            // Copy char to sheet
-            for (int y = 0; y < height; y++)
-            {
-                int destOffset   = (y + posY) * SheetWidth + posX;
-                int sourceOffset = y * width;
-
-                for (int x = 0; x < width; x++)
-                {
-                    rawCharMap[destOffset + x] = (uint)((0xFFFFFF << 8) | data[sourceOffset + x]);
-                }
-            }
-
-            // Generate character info
-            CharacterInfo charInfo = new CharacterInfo()
-            {
-                Left   = (float)posX / SheetWidth,
-                Right  = (float)(posX + width) / SheetWidth,
-                Top    = (float)(posY - 1) / SheetHeight,
-                Bottom = (float)(posY + height) / SheetHeight,
-                Width  = width,
-                Height = height,
-            };
-
-            // Update x
-            posX += width + 1;
-
-            // Give the memory back
-            Marshal.FreeHGlobal(surface.Bits);
-            return charInfo;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
index 2f9c77727..10d66cd28 100644
--- a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
+++ b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
@@ -12,12 +12,12 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
index 36310f3d2..f99f504a7 100644
--- a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
+++ b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
@@ -12,12 +12,12 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj
index 83ec2e964..b256cc6c8 100644
--- a/Ryujinx.Tests/Ryujinx.Tests.csproj
+++ b/Ryujinx.Tests/Ryujinx.Tests.csproj
@@ -17,12 +17,12 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
diff --git a/Ryujinx.sln b/Ryujinx.sln
index 4ad74077c..f023368ba 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -10,9 +10,6 @@ EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}"
-	ProjectSection(ProjectDependencies) = postProject
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} = {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}
-	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}"
 EndProject
@@ -22,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.cs
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx.Profiler\Ryujinx.Profiler.csproj", "{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}"
@@ -37,6 +32,7 @@ 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
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -110,14 +106,6 @@ Global
 		{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
 		{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.Build.0 = Release|Any CPU
 		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -174,6 +162,14 @@ Global
 		{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU
 		{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU
+		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU
+		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU
+		{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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index 9bab74a26..5ce33a9dd 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -1,7 +1,7 @@
 using Gtk;
 using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
-using Ryujinx.Profiler;
+using Ryujinx.Debugger.Profiler;
 using Ryujinx.Ui;
 using System;
 using System.IO;
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 1ff980015..cc8e6d545 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -9,12 +9,12 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>true</Optimize>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
-    <DefineConstants>TRACE;USE_PROFILING</DefineConstants>
+    <DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
     <Optimize>false</Optimize>
   </PropertyGroup>
 
@@ -72,7 +72,7 @@
 
   <ItemGroup>
     <PackageReference Include="DiscordRichPresence" Version="1.0.147" />
-    <PackageReference Include="GtkSharp" Version="3.22.25.24" />
+    <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" />
   </ItemGroup>
@@ -80,8 +80,8 @@
   <ItemGroup>
     <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
     <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+    <ProjectReference Include="..\Ryujinx.Debugger\Ryujinx.Debugger.csproj" />
     <ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
-    <ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
     <ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
     <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs
index 295cf22f4..5e83458ae 100644
--- a/Ryujinx/Ui/GLScreen.cs
+++ b/Ryujinx/Ui/GLScreen.cs
@@ -5,8 +5,6 @@ using Ryujinx.Configuration;
 using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE;
 using Ryujinx.HLE.Input;
-using Ryujinx.Profiler.UI;
-using Ryujinx.Ui;
 using System;
 using System.Threading;
 
@@ -41,10 +39,6 @@ namespace Ryujinx.Ui
 
         private string _newTitle;
 
-#if USE_PROFILING
-        private ProfileWindowManager _profileWindow;
-#endif
-
         public GlScreen(Switch device)
             : base(1280, 720,
             new GraphicsMode(), "Ryujinx", 0,
@@ -65,11 +59,6 @@ namespace Ryujinx.Ui
             Location = new Point(
                 (DisplayDevice.Default.Width  / 2) - (Width  / 2),
                 (DisplayDevice.Default.Height / 2) - (Height / 2));
-
-#if USE_PROFILING
-            // Start profile window, it will handle itself from there
-            _profileWindow = new ProfileWindowManager();
-#endif
         }
 
         private void RenderLoop()
@@ -171,11 +160,6 @@ namespace Ryujinx.Ui
             {
                 KeyboardState keyboard = _keyboard.Value;
 
-#if USE_PROFILING
-                // Profiler input, lets the profiler get access to the main windows keyboard state
-                _profileWindow.UpdateKeyInput(keyboard);
-#endif
-
                 // Normal Input
                 currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
                 currentButton        = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
@@ -330,10 +314,6 @@ namespace Ryujinx.Ui
 
         protected override void OnUnload(EventArgs e)
         {
-#if USE_PROFILING
-            _profileWindow.Close();
-#endif
-
             _renderThread.Join();
 
             base.OnUnload(e);
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 61c59d356..84c736bef 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -3,11 +3,12 @@ using JsonPrettyPrinterPlus;
 using Ryujinx.Audio;
 using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
+using Ryujinx.Debugger.Profiler;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
-using Ryujinx.Profiler;
+using Ryujinx.HLE.FileSystem;
 using System;
 using System.Diagnostics;
 using System.IO;
@@ -36,9 +37,12 @@ namespace Ryujinx.Ui
         private static bool _updatingGameTable;
         private static bool _gameLoaded;
         private static bool _ending;
+        private static bool _debuggerOpened;
 
         private static TreeView _treeView;
 
+        private static Debugger.Debugger _debugger;
+
 #pragma warning disable CS0649
 #pragma warning disable IDE0044
         [GUI] Window        _mainWin;
@@ -61,6 +65,8 @@ namespace Ryujinx.Ui
         [GUI] Label         _progressLabel;
         [GUI] Label         _firmwareVersionLabel;
         [GUI] LevelBar      _progressBar;
+        [GUI] MenuItem      _openDebugger;
+        [GUI] MenuItem      _toolsMenu;
 #pragma warning restore CS0649
 #pragma warning restore IDE0044
 
@@ -118,6 +124,13 @@ namespace Ryujinx.Ui
             if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn)   _fileSizeToggle.Active   = true;
             if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn)       _pathToggle.Active       = true;
 
+#if USE_DEBUGGING
+            _debugger = new Debugger.Debugger();
+            _openDebugger.Activated += _openDebugger_Opened;
+#else
+            _openDebugger.Visible = false;
+#endif
+
             _gameTable.Model = _tableStore = new ListStore(
                 typeof(bool),
                 typeof(Gdk.Pixbuf),
@@ -141,6 +154,36 @@ namespace Ryujinx.Ui
             Task.Run(RefreshFirmwareLabel);
         }
 
+#if USE_DEBUGGING
+        private void _openDebugger_Opened(object sender, EventArgs e)
+        {
+            if (_debuggerOpened)
+            {
+                return;
+            }
+
+            Window debugWindow = new Window("Debugger");
+            
+            debugWindow.SetSizeRequest(1280, 640);
+            debugWindow.Child = _debugger.Widget;
+            debugWindow.DeleteEvent += DebugWindow_DeleteEvent;
+            debugWindow.ShowAll();
+
+            _debugger.Enable();
+
+            _debuggerOpened = true;
+        }
+
+        private void DebugWindow_DeleteEvent(object o, DeleteEventArgs args)
+        {
+            _debuggerOpened = false;
+
+            _debugger.Disable();
+
+            (_debugger.Widget.Parent as Window)?.Remove(_debugger.Widget);
+        }
+#endif
+
         internal static void ApplyTheme()
         {
             if (!ConfigurationState.Instance.Ui.EnableCustomTheme)
@@ -307,7 +350,15 @@ namespace Ryujinx.Ui
 #if MACOS_BUILD
                 CreateGameWindow(device);
 #else
-                new Thread(() => CreateGameWindow(device)).Start();
+                var windowThread = new Thread(() =>
+                {
+                    CreateGameWindow(device);
+                })
+                {
+                    Name = "GUI.WindowThread"
+                };
+
+                windowThread.Start();
 #endif
 
                 _gameLoaded              = true;
@@ -366,6 +417,11 @@ namespace Ryujinx.Ui
 
         private void End(HLE.Switch device)
         {
+
+#if USE_DEBUGGING
+            _debugger.Dispose();
+#endif
+
             if (_ending)
             {
                 return;
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index 8e2eab939..d3cdc5934 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.21.0 -->
+<!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkApplicationWindow" id="_mainWin">
@@ -8,6 +8,9 @@
     <property name="window_position">center</property>
     <property name="default_width">1280</property>
     <property name="default_height">750</property>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
     <child>
       <object class="GtkBox" id="_box">
         <property name="visible">True</property>
@@ -255,7 +258,7 @@
               </object>
             </child>
             <child>
-              <object class="GtkMenuItem" id="ToolsMenu">
+              <object class="GtkMenuItem" id="_toolsMenu">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="label" translatable="yes">Tools</property>
@@ -296,6 +299,14 @@
                         </child>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkMenuItem" id="_openDebugger">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Open Debugger</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
@@ -499,8 +510,5 @@
         </child>
       </object>
     </child>
-    <child type="titlebar">
-      <placeholder/>
-    </child>
   </object>
 </interface>