diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index c75754fce..a461355c5 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -115,6 +115,9 @@ add_executable(citra-qt
     game_list_worker.h
     hotkeys.cpp
     hotkeys.h
+    loading_screen.cpp
+    loading_screen.h
+    loading_screen.ui
     main.cpp
     main.h
     main.ui
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index f28fcdf10..22b6bcdd7 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -46,12 +46,15 @@ void EmuThread::run() {
     MicroProfileOnThreadCreate("EmuThread");
     Frontend::ScopeAcquireContext scope(core_context);
 
+    emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+
     Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
         stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
-            LOG_DEBUG(Frontend, "Loading stage {} progress {} {}", static_cast<u32>(stage), value,
-                      total);
+            emit LoadProgress(stage, value, total);
         });
 
+    emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+
     // Holds whether the cpu was running during the last iteration,
     // so that the DebugModeLeft signal can be emitted before the
     // next execution step.
@@ -127,6 +130,7 @@ OpenGLWindow::~OpenGLWindow() {
 void OpenGLWindow::Present() {
     if (!isExposed())
         return;
+
     context->makeCurrent(this);
     VideoCore::g_renderer->TryPresent(100);
     context->swapBuffers(this);
@@ -182,8 +186,8 @@ void OpenGLWindow::exposeEvent(QExposeEvent* event) {
     QWindow::exposeEvent(event);
 }
 
-GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
-    : QWidget(parent), emu_thread(emu_thread) {
+GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
+    : QWidget(parent_), emu_thread(emu_thread) {
 
     setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
                        .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
@@ -192,6 +196,9 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
     layout->setMargin(0);
     setLayout(layout);
     InputCommon::Init();
+
+    GMainWindow* parent = GetMainWindow();
+    connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
 }
 
 GRenderWindow::~GRenderWindow() {
@@ -206,7 +213,12 @@ void GRenderWindow::DoneCurrent() {
     core_context->DoneCurrent();
 }
 
-void GRenderWindow::PollEvents() {}
+void GRenderWindow::PollEvents() {
+    if (!first_frame) {
+        first_frame = true;
+        emit FirstFrameDisplayed();
+    }
+}
 
 // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
 //
@@ -363,12 +375,15 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
 void GRenderWindow::InitRenderTarget() {
     ReleaseRenderTarget();
 
+    first_frame = false;
+
     GMainWindow* parent = GetMainWindow();
     QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
     child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
     child_window->create();
     child_widget = createWindowContainer(child_window, this);
     child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
+
     layout()->addWidget(child_widget);
 
     core_context = CreateSharedContext();
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 922e093f9..ab9bd7197 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -23,6 +23,10 @@ class QOpenGLContext;
 class GMainWindow;
 class GRenderWindow;
 
+namespace VideoCore {
+enum class LoadCallbackStage;
+}
+
 class GLContext : public Frontend::GraphicsContext {
 public:
     explicit GLContext(QOpenGLContext* shared_context);
@@ -116,6 +120,8 @@ signals:
     void DebugModeLeft();
 
     void ErrorThrown(Core::System::ResultStatus, std::string);
+
+    void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
 };
 
 class OpenGLWindow : public QWindow {
@@ -188,6 +194,11 @@ signals:
     /// Emitted when the window is closed
     void Closed();
 
+    /**
+     * Emitted when the guest first calls SwapBuffers. This is used to hide the loading screen
+     */
+    void FirstFrameDisplayed();
+
 private:
     std::pair<u32, u32> ScaleTouch(QPointF pos) const;
     void TouchBeginEvent(const QTouchEvent* event);
@@ -212,6 +223,7 @@ private:
 
     /// Temporary storage of the screenshot taken
     QImage screenshot_image;
+    bool first_frame = false;
 
 protected:
     void showEvent(QShowEvent* event) override;
diff --git a/src/citra_qt/loading_screen.cpp b/src/citra_qt/loading_screen.cpp
new file mode 100644
index 000000000..8349245b5
--- /dev/null
+++ b/src/citra_qt/loading_screen.cpp
@@ -0,0 +1,212 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <unordered_map>
+#include <QBuffer>
+#include <QByteArray>
+#include <QGraphicsOpacityEffect>
+#include <QHBoxLayout>
+#include <QIODevice>
+#include <QImage>
+#include <QLabel>
+#include <QPainter>
+#include <QPalette>
+#include <QPixmap>
+#include <QProgressBar>
+#include <QPropertyAnimation>
+#include <QStyleOption>
+#include <QTime>
+#include <QtConcurrent/QtConcurrentRun>
+#include "citra_qt/loading_screen.h"
+#include "common/logging/log.h"
+#include "core/loader/loader.h"
+#include "core/loader/smdh.h"
+#include "ui_loading_screen.h"
+#include "video_core/rasterizer_interface.h"
+
+constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"(
+QProgressBar {}
+QProgressBar::chunk {})";
+
+constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"(
+QProgressBar {
+  background-color: black;
+  border: 2px solid white;
+  border-radius: 4px;
+  padding: 2px;
+}
+QProgressBar::chunk {
+  background-color: #fd8507;
+  width: 1px;
+})";
+
+constexpr char PROGRESSBAR_STYLE_BUILD[] = R"(
+QProgressBar {
+  background-color: black;
+  border: 2px solid white;
+  border-radius: 4px;
+  padding: 2px;
+}
+QProgressBar::chunk {
+  background-color: #ffe402;
+  width: 1px;
+})";
+
+constexpr char PROGRESSBAR_STYLE_COMPLETE[] = R"(
+QProgressBar {
+  background-color: #fd8507;
+  border: 2px solid white;
+  border-radius: 4px;
+  padding: 2px;
+}
+QProgressBar::chunk {
+  background-color: #ffe402;
+})";
+
+// Definitions for the differences in text and styling for each stage
+const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> stage_translations{
+    {VideoCore::LoadCallbackStage::Prepare, QT_TRANSLATE_NOOP("LoadingScreen", "Loading...")},
+    {VideoCore::LoadCallbackStage::Decompile,
+     QT_TRANSLATE_NOOP("LoadingScreen", "Preparing Shaders %1 / %2")},
+    {VideoCore::LoadCallbackStage::Build,
+     QT_TRANSLATE_NOOP("LoadingScreen", "Loading Shaders %1 / %2")},
+    {VideoCore::LoadCallbackStage::Complete, QT_TRANSLATE_NOOP("LoadingScreen", "Launching...")},
+};
+const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> progressbar_style{
+    {VideoCore::LoadCallbackStage::Prepare, PROGRESSBAR_STYLE_PREPARE},
+    {VideoCore::LoadCallbackStage::Decompile, PROGRESSBAR_STYLE_DECOMPILE},
+    {VideoCore::LoadCallbackStage::Build, PROGRESSBAR_STYLE_BUILD},
+    {VideoCore::LoadCallbackStage::Complete, PROGRESSBAR_STYLE_COMPLETE},
+};
+
+static QPixmap GetQPixmapFromSMDH(std::vector<u8>& smdh_data) {
+    Loader::SMDH smdh;
+    memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
+
+    bool large = true;
+    std::vector<u16> icon_data = smdh.GetIcon(large);
+    const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
+    int size = large ? 48 : 24;
+    QImage icon(data, size, size, QImage::Format::Format_RGB16);
+    return QPixmap::fromImage(icon);
+}
+
+LoadingScreen::LoadingScreen(QWidget* parent)
+    : QWidget(parent), ui(std::make_unique<Ui::LoadingScreen>()),
+      previous_stage(VideoCore::LoadCallbackStage::Complete) {
+    ui->setupUi(this);
+    setMinimumSize(400, 240);
+
+    // Create a fade out effect to hide this loading screen widget.
+    // When fading opacity, it will fade to the parent widgets background color, which is why we
+    // create an internal widget named fade_widget that we use the effect on, while keeping the
+    // loading screen widget's background color black. This way we can create a fade to black effect
+    opacity_effect = new QGraphicsOpacityEffect(this);
+    opacity_effect->setOpacity(1);
+    ui->fade_parent->setGraphicsEffect(opacity_effect);
+    fadeout_animation = std::make_unique<QPropertyAnimation>(opacity_effect, "opacity");
+    fadeout_animation->setDuration(500);
+    fadeout_animation->setStartValue(1);
+    fadeout_animation->setEndValue(0);
+    fadeout_animation->setEasingCurve(QEasingCurve::OutBack);
+
+    // After the fade completes, hide the widget and reset the opacity
+    connect(fadeout_animation.get(), &QPropertyAnimation::finished, [this] {
+        hide();
+        opacity_effect->setOpacity(1);
+        emit Hidden();
+    });
+    connect(this, &LoadingScreen::LoadProgress, this, &LoadingScreen::OnLoadProgress,
+            Qt::QueuedConnection);
+    qRegisterMetaType<VideoCore::LoadCallbackStage>();
+}
+
+LoadingScreen::~LoadingScreen() = default;
+
+void LoadingScreen::Prepare(Loader::AppLoader& loader) {
+    std::vector<u8> buffer;
+    // TODO when banner becomes supported, decode it and add it as a movie
+
+    if (loader.ReadIcon(buffer) == Loader::ResultStatus::Success) {
+        QPixmap icon = GetQPixmapFromSMDH(buffer);
+        ui->icon->setPixmap(icon);
+    }
+    std::string title;
+    if (loader.ReadTitle(title) == Loader::ResultStatus::Success) {
+        ui->title->setText(QString("Now Loading\n") + QString::fromStdString(title));
+    }
+    eta_shown = false;
+    OnLoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+}
+
+void LoadingScreen::OnLoadComplete() {
+    fadeout_animation->start(QPropertyAnimation::KeepWhenStopped);
+}
+
+void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
+                                   std::size_t total) {
+    using namespace std::chrono;
+    const auto now = high_resolution_clock::now();
+    // reset the timer if the stage changes
+    if (stage != previous_stage) {
+        ui->progress_bar->setStyleSheet(progressbar_style.at(stage));
+        // Hide the progress bar during the prepare stage
+        if (stage == VideoCore::LoadCallbackStage::Prepare) {
+            ui->progress_bar->hide();
+        } else {
+            ui->progress_bar->show();
+        }
+        previous_stage = stage;
+    }
+    // update the max of the progress bar if the number of shaders change
+    if (total != previous_total) {
+        ui->progress_bar->setMaximum(static_cast<int>(total));
+        previous_total = total;
+    }
+
+    // calculate a simple rolling average after the first shader is loaded
+    if (value > 0) {
+        rolling_average -= rolling_average / NumberOfDataPoints;
+        rolling_average += (now - previous_time) / NumberOfDataPoints;
+    }
+
+    QString estimate;
+
+    // After 25 shader load times were put into the rolling average, determine if the ETA is long
+    // enough to show it
+    if (value > NumberOfDataPoints &&
+        (eta_shown || rolling_average * (total - value) > ETABreakPoint)) {
+        if (!eta_shown) {
+            eta_shown = true;
+        }
+        const auto eta_mseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
+            rolling_average * (total - value));
+        estimate = tr("Estimated Time %1")
+                       .arg(QTime(0, 0, 0, 0)
+                                .addMSecs(std::max<long>(eta_mseconds.count(), 1000))
+                                .toString(QStringLiteral("mm:ss")));
+    }
+
+    // update labels and progress bar
+    const auto& stg = tr(stage_translations.at(stage));
+    if (stage == VideoCore::LoadCallbackStage::Decompile ||
+        stage == VideoCore::LoadCallbackStage::Build) {
+        ui->stage->setText(stg.arg(value).arg(total));
+    } else {
+        ui->stage->setText(stg);
+    }
+    ui->value->setText(estimate);
+    ui->progress_bar->setValue(static_cast<int>(value));
+    previous_time = now;
+}
+
+void LoadingScreen::paintEvent(QPaintEvent* event) {
+    QStyleOption opt;
+    opt.init(this);
+    QPainter p(this);
+    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+    QWidget::paintEvent(event);
+}
+
+void LoadingScreen::Clear() {}
diff --git a/src/citra_qt/loading_screen.h b/src/citra_qt/loading_screen.h
new file mode 100644
index 000000000..59e841f9d
--- /dev/null
+++ b/src/citra_qt/loading_screen.h
@@ -0,0 +1,78 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <QString>
+#include <QWidget>
+
+namespace Loader {
+class AppLoader;
+}
+
+namespace Ui {
+class LoadingScreen;
+}
+
+namespace VideoCore {
+enum class LoadCallbackStage;
+}
+
+class QGraphicsOpacityEffect;
+class QPropertyAnimation;
+
+class LoadingScreen : public QWidget {
+    Q_OBJECT
+
+public:
+    explicit LoadingScreen(QWidget* parent = nullptr);
+
+    ~LoadingScreen();
+
+    /// Call before showing the loading screen to load the widgets with the logo and banner for the
+    /// currently loaded application.
+    void Prepare(Loader::AppLoader& loader);
+
+    /// After the loading screen is hidden, the owner of this class can call this to clean up any
+    /// used resources such as the logo and banner.
+    void Clear();
+
+    /// Slot used to update the status of the progress bar
+    void OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
+
+    /// Hides the LoadingScreen with a fade out effect
+    void OnLoadComplete();
+
+    // In order to use a custom widget with a stylesheet, you need to override the paintEvent
+    // See https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget
+    void paintEvent(QPaintEvent* event) override;
+
+signals:
+    void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
+    /// Signals that this widget is completely hidden now and should be replaced with the other
+    /// widget
+    void Hidden();
+
+private:
+    std::unique_ptr<Ui::LoadingScreen> ui;
+    std::size_t previous_total = 0;
+    VideoCore::LoadCallbackStage previous_stage;
+
+    QGraphicsOpacityEffect* opacity_effect = nullptr;
+    std::unique_ptr<QPropertyAnimation> fadeout_animation;
+
+    // Variables used to keep track of the current ETA.
+    // If the rolling_average * shaders_remaining > eta_break_point then we want to display the eta.
+    // We don't want to always display it since showing an ETA leads people to think its taking
+    // longer that it is because ETAs are often wrong
+    static constexpr std::chrono::seconds ETABreakPoint = std::chrono::seconds{10};
+    static constexpr std::size_t NumberOfDataPoints = 25;
+    std::chrono::high_resolution_clock::time_point previous_time;
+    std::chrono::duration<double> rolling_average = {};
+    bool eta_shown = false;
+};
+
+Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
diff --git a/src/citra_qt/loading_screen.ui b/src/citra_qt/loading_screen.ui
new file mode 100644
index 000000000..769f3a35d
--- /dev/null
+++ b/src/citra_qt/loading_screen.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoadingScreen</class>
+ <widget class="QWidget" name="LoadingScreen">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>746</width>
+    <height>495</height>
+   </rect>
+  </property>
+  <property name="styleSheet">
+   <string notr="true">background-color: rgb(0, 0, 0);</string>
+  </property>
+  <layout class="QVBoxLayout">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QWidget" name="fade_parent" native="true">
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,1,0">
+        <property name="spacing">
+         <number>15</number>
+        </property>
+        <property name="sizeConstraint">
+         <enum>QLayout::SetNoConstraint</enum>
+        </property>
+        <item>
+         <spacer name="verticalSpacer_2">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QLabel" name="icon">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+          <property name="margin">
+           <number>5</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="title">
+          <property name="styleSheet">
+           <string notr="true">background-color: black; color: white;
+font: 75 20pt &quot;Arial&quot;;</string>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+         </widget>
+        </item>
+        <item alignment="Qt::AlignHCenter|Qt::AlignBottom">
+         <widget class="QLabel" name="stage">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">background-color: black; color: white;
+font: 75 20pt &quot;Arial&quot;;</string>
+          </property>
+          <property name="text">
+           <string>Loading Shaders 387 / 1628</string>
+          </property>
+         </widget>
+        </item>
+        <item alignment="Qt::AlignHCenter|Qt::AlignTop">
+         <widget class="QProgressBar" name="progress_bar">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>500</width>
+            <height>40</height>
+           </size>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">QProgressBar {
+color: white;
+border: 2px solid white;
+outline-color: black;
+border-radius: 20px;
+}
+QProgressBar::chunk {
+background-color: white;
+border-radius: 15px;
+}</string>
+          </property>
+          <property name="value">
+           <number>50</number>
+          </property>
+          <property name="textVisible">
+           <bool>false</bool>
+          </property>
+          <property name="format">
+           <string>Loading Shaders %v out of %m</string>
+          </property>
+         </widget>
+        </item>
+        <item alignment="Qt::AlignHCenter|Qt::AlignTop">
+         <widget class="QLabel" name="value">
+          <property name="toolTip">
+           <string notr="true"/>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">background-color: black; color: white;
+font: 75 15pt &quot;Arial&quot;;</string>
+          </property>
+          <property name="text">
+           <string>Estimated Time 5m 4s</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="verticalSpacer">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 863d0c5b8..33d3d66a1 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -47,6 +47,7 @@
 #include "citra_qt/discord.h"
 #include "citra_qt/game_list.h"
 #include "citra_qt/hotkeys.h"
+#include "citra_qt/loading_screen.h"
 #include "citra_qt/main.h"
 #include "citra_qt/multiplayer/state.h"
 #include "citra_qt/qt_image_interface.h"
@@ -222,6 +223,17 @@ void GMainWindow::InitializeWidgets() {
     ui.horizontalLayout->addWidget(game_list_placeholder);
     game_list_placeholder->setVisible(false);
 
+    loading_screen = new LoadingScreen(this);
+    loading_screen->hide();
+    ui.horizontalLayout->addWidget(loading_screen);
+    connect(loading_screen, &LoadingScreen::Hidden, [&] {
+        loading_screen->Clear();
+        if (emulation_running) {
+            render_window->show();
+            render_window->setFocus();
+        }
+    });
+
     multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui.action_Leave_Room,
                                              ui.action_Show_Room);
     multiplayer_state->setVisible(false);
@@ -917,6 +929,9 @@ void GMainWindow::BootGame(const QString& filename) {
     connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
             &WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
 
+    connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
+            &LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
+
     // Update the GUI
     registersWidget->OnDebugModeEntered();
     if (ui.action_Single_Window_Mode->isChecked()) {
@@ -925,8 +940,12 @@ void GMainWindow::BootGame(const QString& filename) {
     }
     status_bar_update_timer.start(2000);
 
+    // show and hide the render_window to create the context
     render_window->show();
-    render_window->setFocus();
+    render_window->hide();
+
+    loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
+    loading_screen->show();
 
     emulation_running = true;
     if (ui.action_Fullscreen->isChecked()) {
@@ -1003,6 +1022,8 @@ void GMainWindow::ShutdownGame() {
     ui.action_Advance_Frame->setEnabled(false);
     ui.action_Capture_Screenshot->setEnabled(false);
     render_window->hide();
+    loading_screen->hide();
+    loading_screen->Clear();
     if (game_list->isEmpty())
         game_list_placeholder->show();
     else
@@ -1326,6 +1347,10 @@ void GMainWindow::OnStopGame() {
     ShutdownGame();
 }
 
+void GMainWindow::OnLoadComplete() {
+    loading_screen->OnLoadComplete();
+}
+
 void GMainWindow::OnMenuReportCompatibility() {
     if (!Settings::values.citra_token.empty() && !Settings::values.citra_username.empty()) {
         CompatDB compatdb{this};
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 7ec4fa185..91d7eed1b 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -32,6 +32,7 @@ class GraphicsVertexShaderWidget;
 class GRenderWindow;
 class IPCRecorderWidget;
 class LLEServiceModulesWidget;
+class LoadingScreen;
 class MicroProfileDialog;
 class MultiplayerState;
 class ProfilerWidget;
@@ -75,6 +76,7 @@ public:
 
 public slots:
     void OnAppFocusStateChanged(Qt::ApplicationState state);
+    void OnLoadComplete();
 
 signals:
 
@@ -221,6 +223,7 @@ private:
     GRenderWindow* render_window;
 
     GameListPlaceholder* game_list_placeholder;
+    LoadingScreen* loading_screen;
 
     // Status bar elements
     QProgressBar* progress_bar = nullptr;