diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index 266bdba6f1..c931aaead3 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -70,6 +70,10 @@ namespace ARMeilleure.Translation.PTC
 
         internal static PtcState State { get; private set; }
 
+        // Progress update events
+        public static event Action<bool> PtcTranslationStateChanged;
+        public static event Action<int, int> PtcTranslationProgressChanged;
+
         static Ptc()
         {
             InitializeMemoryStreams();
@@ -772,6 +776,8 @@ namespace ARMeilleure.Translation.PTC
 
             ThreadPool.QueueUserWorkItem(TranslationLogger, profiledFuncsToTranslate.Count);
 
+            PtcTranslationStateChanged?.Invoke(true);
+
             void TranslateFuncs()
             {
                 while (profiledFuncsToTranslate.TryDequeue(out var item))
@@ -820,6 +826,7 @@ namespace ARMeilleure.Translation.PTC
             threads.Clear();
 
             _loggerEvent.Set();
+            PtcTranslationStateChanged?.Invoke(false);
 
             PtcJumpTable.Initialize(jumpTable);
 
@@ -833,15 +840,15 @@ namespace ARMeilleure.Translation.PTC
 
         private static void TranslationLogger(object state)
         {
-            const int refreshRate = 1; // Seconds.
+            const int refreshRate = 100; // ms
 
             int profiledFuncsToTranslateCount = (int)state;
 
             do
             {
-                Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
+                PtcTranslationProgressChanged?.Invoke(_translateCount, profiledFuncsToTranslateCount);
             }
-            while (!_loggerEvent.WaitOne(refreshRate * 1000));
+            while (!_loggerEvent.WaitOne(refreshRate));
 
             Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
         }
diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs
index 15f757c87a..06be6e0579 100644
--- a/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -79,6 +79,26 @@ namespace Ryujinx.Graphics.Gpu
         /// </summary>
         internal Capabilities Capabilities => _caps.Value;
 
+        /// <summary>
+        /// Signaled when shader cache begins and ends loading.
+        /// Signals true when loading has started, false when ended.
+        /// </summary>
+        public event Action<bool> ShaderCacheStateChanged
+        {
+            add => Methods.ShaderCache.ShaderCacheStateChanged += value;
+            remove => Methods.ShaderCache.ShaderCacheStateChanged -= value;
+        }
+
+        /// <summary>
+        /// Signaled while shader cache is loading to indicate current progress.
+        /// Provides current and total number of shaders loaded.
+        /// </summary>
+        public event Action<int, int> ShaderCacheProgressChanged
+        {
+            add => Methods.ShaderCache.ShaderCacheProgressChanged += value;
+            remove => Methods.ShaderCache.ShaderCacheProgressChanged -= value;
+        }
+
         /// <summary>
         /// Creates a new instance of the GPU emulation context.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 6a3971df27..bf89f29d6f 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -9,6 +9,7 @@ using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Threading;
 
 namespace Ryujinx.Graphics.Gpu.Shader
 {
@@ -36,6 +37,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         private const ulong ShaderCodeGenVersion = 1961;
 
+        // Progress reporting helpers
+        private int _shaderCount;
+        private readonly AutoResetEvent _progressReportEvent;
+        public event Action<bool> ShaderCacheStateChanged;
+        public event Action<int, int> ShaderCacheProgressChanged;
+
         /// <summary>
         /// Creates a new instance of the shader cache.
         /// </summary>
@@ -50,6 +57,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
             _gpPrograms = new Dictionary<ShaderAddresses, List<ShaderBundle>>();
             _gpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
             _cpProgramsDiskCache = new Dictionary<Hash128, ShaderBundle>();
+
+            _progressReportEvent = new AutoResetEvent(false);
         }
 
         /// <summary>
@@ -76,12 +85,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                 ReadOnlySpan<Hash128> guestProgramList = _cacheManager.GetGuestProgramList();
 
+                _progressReportEvent.Reset();
+                _shaderCount = 0;
+
+                ShaderCacheStateChanged?.Invoke(true);
+                ThreadPool.QueueUserWorkItem(ProgressLogger, guestProgramList.Length);
+
                 for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
                 {
                     Hash128 key = guestProgramList[programIndex];
 
-                    Logger.Info?.Print(LogClass.Gpu, $"Compiling shader {key} ({programIndex + 1} / {guestProgramList.Length})");
-
                     byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key);
                     bool hasHostCache = hostProgramBinary != null;
 
@@ -304,6 +317,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                         _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
                     }
+
+                    _shaderCount = programIndex;
                 }
 
                 if (!isReadOnly)
@@ -314,10 +329,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     _cacheManager.Synchronize();
                 }
 
-                Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
+                _progressReportEvent.Set();
+                ShaderCacheStateChanged?.Invoke(false);
+
+                Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries.");
             }
         }
 
+        /// <summary>
+        /// Raises ShaderCacheProgressChanged events periodically.
+        /// </summary>
+        private void ProgressLogger(object state)
+        {
+            const int refreshRate = 100; // ms
+
+            int totalCount = (int)state;
+            do
+            {
+                ShaderCacheProgressChanged?.Invoke(_shaderCount, totalCount);
+            }
+            while (!_progressReportEvent.WaitOne(refreshRate));
+        }
+
         /// <summary>
         /// Gets a compute shader from the cache.
         /// </summary>
@@ -787,6 +820,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 }
             }
 
+            _progressReportEvent?.Dispose();
             _cacheManager?.Dispose();
         }
     }
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 809b693b9d..7d48422c12 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -92,10 +92,14 @@ namespace Ryujinx.Ui
         [GUI] Label           _gpuName;
         [GUI] Label           _progressLabel;
         [GUI] Label           _firmwareVersionLabel;
-        [GUI] LevelBar        _progressBar;
+        [GUI] ProgressBar     _progressBar;
         [GUI] Box             _viewBox;
         [GUI] Label           _vSyncStatus;
         [GUI] Box             _listStatusBox;
+        [GUI] Label           _loadingStatusLabel;
+        [GUI] ProgressBar     _loadingStatusBar;
+
+        private string        _loadingStatusTitle = "";
 
 #pragma warning restore CS0649, IDE0044, CS0169
 
@@ -333,6 +337,48 @@ namespace Ryujinx.Ui
             _emulationContext.Initialize();
         }
 
+        private void SetupProgressUiHandlers()
+        {
+            Ptc.PtcTranslationStateChanged -= PtcStatusChanged;
+            Ptc.PtcTranslationStateChanged += PtcStatusChanged;
+
+            Ptc.PtcTranslationProgressChanged -= LoadingProgressChanged;
+            Ptc.PtcTranslationProgressChanged += LoadingProgressChanged;
+
+            _emulationContext.Gpu.ShaderCacheStateChanged -= ShaderCacheStatusChanged;
+            _emulationContext.Gpu.ShaderCacheStateChanged += ShaderCacheStatusChanged;
+
+            _emulationContext.Gpu.ShaderCacheProgressChanged -= LoadingProgressChanged;
+            _emulationContext.Gpu.ShaderCacheProgressChanged += LoadingProgressChanged;
+        }
+
+        private void ShaderCacheStatusChanged(bool state)
+        {
+            _loadingStatusTitle = "Shaders";
+            Application.Invoke(delegate
+            {
+                _loadingStatusBar.Visible = _loadingStatusLabel.Visible = state;
+            });
+        }
+
+        private void PtcStatusChanged(bool state)
+        {
+            _loadingStatusTitle = "PTC";
+            Application.Invoke(delegate
+            {
+                _loadingStatusBar.Visible = _loadingStatusLabel.Visible = state;
+            });
+        }
+
+        private void LoadingProgressChanged(int value, int total)
+        {
+            Application.Invoke(delegate
+            {
+                _loadingStatusBar.Fraction = (double)value / total;
+                _loadingStatusLabel.Text = $"{_loadingStatusTitle} : {value}/{total}";
+            });
+        }
+
         public void UpdateGameTable()
         {
             if (_updatingGameTable || _gameLoaded)
@@ -531,6 +577,8 @@ namespace Ryujinx.Ui
 
                 _deviceExitStatus.Reset();
 
+                SetupProgressUiHandlers();
+
                 Translator.IsReadyForTranslation.Reset();
 #if MACOS_BUILD
                 CreateGameWindow();
@@ -775,7 +823,7 @@ namespace Ryujinx.Ui
                     barValue = (float)args.NumAppsLoaded / args.NumAppsFound;
                 }
 
-                _progressBar.Value = barValue;
+                _progressBar.Fraction = barValue;
 
                 // Reset the vertical scrollbar to the top when titles finish loading
                 if (args.NumAppsLoaded == args.NumAppsFound)
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index 688f0e2553..5558403bac 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -459,13 +459,14 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkLevelBar" id="_progressBar">
+                      <object class="GtkProgressBar" id="_progressBar">
                         <property name="width_request">200</property>
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="halign">start</property>
                         <property name="margin_left">10</property>
                         <property name="margin_right">5</property>
+                        <property name="margin_bottom">6</property>
                       </object>
                       <packing>
                         <property name="expand">True</property>
@@ -640,6 +641,7 @@
                         <property name="can_focus">False</property>
                         <property name="halign">start</property>
                         <property name="margin_left">5</property>
+                        <property name="margin_right">5</property>
                       </object>
                       <packing>
                         <property name="expand">True</property>
@@ -654,16 +656,34 @@
                     <property name="position">1</property>
                   </packing>
                 </child>
-                <child>
-                  <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">3</property>
-                  </packing>
+                  <child>
+                    <object class="GtkLabel" id="_loadingStatusLabel">
+                      <property name="can_focus">False</property>
+                      <property name="margin_left">5</property>
+                      <property name="margin_right">5</property>
+                      <property name="label" translatable="yes">0/0 </property>
+                      <property name="visible">False</property>
+                    </object>
+                    <packing>
+                      <property name="expand">False</property>
+                      <property name="fill">True</property>
+                      <property name="position">11</property>
+                    </packing>
+                  </child>
+                  <child>
+                    <object class="GtkProgressBar" id="_loadingStatusBar">
+                      <property name="width_request">200</property>
+                      <property name="can_focus">False</property>
+                      <property name="margin_left">5</property>
+                      <property name="margin_right">5</property>
+                      <property name="margin_bottom">6</property>
+                      <property name="visible">False</property>
+                    </object>
+                    <packing>
+                      <property name="expand">False</property>
+                      <property name="fill">True</property>
+                      <property name="position">12</property>
+                    </packing>
                 </child>
                 <child>
                   <object class="GtkBox">