diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 4982884f57..99e0ac61e4 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -9,6 +9,9 @@
 #include <QKeyEvent>
 #include <QMessageBox>
 #include <QOffscreenSurface>
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+#include <QOpenGLFunctions_4_3_Core>
 #include <QOpenGLWindow>
 #include <QPainter>
 #include <QScreen>
@@ -23,6 +26,7 @@
 #include "common/assert.h"
 #include "common/microprofile.h"
 #include "common/scm_rev.h"
+#include "common/scope_exit.h"
 #include "core/core.h"
 #include "core/frontend/framebuffer_layout.h"
 #include "core/frontend/scope_acquire_context.h"
@@ -35,15 +39,32 @@
 #include "yuzu/bootmanager.h"
 #include "yuzu/main.h"
 
-EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
+EmuThread::EmuThread(Core::Frontend::GraphicsContext& core_context) : core_context(core_context) {}
 
 EmuThread::~EmuThread() = default;
 
-void EmuThread::run() {
-    render_window->MakeCurrent();
+static GMainWindow* GetMainWindow() {
+    for (QWidget* w : qApp->topLevelWidgets()) {
+        if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
+            return main;
+        }
+    }
+    return nullptr;
+}
 
+void EmuThread::run() {
     MicroProfileOnThreadCreate("EmuThread");
 
+    // Acquire render context for duration of the thread if this is the rendering thread
+    if (!Settings::values.use_asynchronous_gpu_emulation) {
+        core_context.MakeCurrent();
+    }
+    SCOPE_EXIT({
+        if (!Settings::values.use_asynchronous_gpu_emulation) {
+            core_context.DoneCurrent();
+        }
+    });
+
     emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
 
     Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@@ -53,11 +74,6 @@ void EmuThread::run() {
 
     emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
 
-    if (Settings::values.use_asynchronous_gpu_emulation) {
-        // Release OpenGL context for the GPU thread
-        render_window->DoneCurrent();
-    }
-
     // Holds whether the cpu was running during the last iteration,
     // so that the DebugModeLeft signal can be emitted before the
     // next execution step
@@ -98,190 +114,157 @@ void EmuThread::run() {
 #if MICROPROFILE_ENABLED
     MicroProfileOnThreadExit();
 #endif
-
-    render_window->moveContext();
 }
 
 class GGLContext : public Core::Frontend::GraphicsContext {
 public:
-    explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} {
-        context.setFormat(shared_context->format());
-        context.setShareContext(shared_context);
-        context.create();
+    explicit GGLContext(QOpenGLContext* shared_context)
+        : context(new QOpenGLContext(shared_context->parent())),
+          surface(new QOffscreenSurface(nullptr)) {
+
+        // disable vsync for any shared contexts
+        auto format = shared_context->format();
+        format.setSwapInterval(0);
+
+        context->setShareContext(shared_context);
+        context->setFormat(format);
+        context->create();
+        surface->setParent(shared_context->parent());
+        surface->setFormat(format);
+        surface->create();
     }
 
     void MakeCurrent() override {
-        context.makeCurrent(shared_context->surface());
+        context->makeCurrent(surface);
     }
 
     void DoneCurrent() override {
-        context.doneCurrent();
-    }
-
-    void SwapBuffers() override {}
-
-private:
-    QOpenGLContext* shared_context;
-    QOpenGLContext context;
-};
-
-class GWidgetInternal : public QWindow {
-public:
-    GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
-    virtual ~GWidgetInternal() = default;
-
-    void resizeEvent(QResizeEvent* ev) override {
-        parent->OnClientAreaResized(ev->size().width(), ev->size().height());
-        parent->OnFramebufferSizeChanged();
-    }
-
-    void keyPressEvent(QKeyEvent* event) override {
-        InputCommon::GetKeyboard()->PressKey(event->key());
-    }
-
-    void keyReleaseEvent(QKeyEvent* event) override {
-        InputCommon::GetKeyboard()->ReleaseKey(event->key());
-    }
-
-    void mousePressEvent(QMouseEvent* event) override {
-        if (event->source() == Qt::MouseEventSynthesizedBySystem)
-            return; // touch input is handled in TouchBeginEvent
-
-        const auto pos{event->pos()};
-        if (event->button() == Qt::LeftButton) {
-            const auto [x, y] = parent->ScaleTouch(pos);
-            parent->TouchPressed(x, y);
-        } else if (event->button() == Qt::RightButton) {
-            InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
-        }
-    }
-
-    void mouseMoveEvent(QMouseEvent* event) override {
-        if (event->source() == Qt::MouseEventSynthesizedBySystem)
-            return; // touch input is handled in TouchUpdateEvent
-
-        const auto pos{event->pos()};
-        const auto [x, y] = parent->ScaleTouch(pos);
-        parent->TouchMoved(x, y);
-        InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
-    }
-
-    void mouseReleaseEvent(QMouseEvent* event) override {
-        if (event->source() == Qt::MouseEventSynthesizedBySystem)
-            return; // touch input is handled in TouchEndEvent
-
-        if (event->button() == Qt::LeftButton)
-            parent->TouchReleased();
-        else if (event->button() == Qt::RightButton)
-            InputCommon::GetMotionEmu()->EndTilt();
-    }
-
-    void DisablePainting() {
-        do_painting = false;
-    }
-
-    void EnablePainting() {
-        do_painting = true;
-    }
-
-    std::pair<unsigned, unsigned> GetSize() const {
-        return std::make_pair(width(), height());
-    }
-
-protected:
-    bool IsPaintingEnabled() const {
-        return do_painting;
+        context->doneCurrent();
     }
 
 private:
-    GRenderWindow* parent;
-    bool do_painting = false;
+    QOpenGLContext* context;
+    QOffscreenSurface* surface;
 };
 
-// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
-// context.
-// The corresponding functionality is handled in EmuThread instead
-class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
-public:
-    GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
-        : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
-    ~GGLWidgetInternal() override = default;
+OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
+    : QWindow(parent), event_handler(event_handler),
+      context(new QOpenGLContext(shared_context->parent())) {
 
-    void paintEvent(QPaintEvent* ev) override {
-        if (IsPaintingEnabled()) {
-            QPainter painter(this);
-        }
+    // disable vsync for any shared contexts
+    auto format = shared_context->format();
+    format.setSwapInterval(Settings::values.use_vsync ? 1 : 0);
+    this->setFormat(format);
+
+    context->setShareContext(shared_context);
+    context->setScreen(this->screen());
+    context->setFormat(format);
+    context->create();
+
+    setSurfaceType(QWindow::OpenGLSurface);
+
+    // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
+    // WA_DontShowOnScreen, WA_DeleteOnClose
+}
+
+OpenGLWindow::~OpenGLWindow() {
+    context->doneCurrent();
+}
+
+void OpenGLWindow::Present() {
+    if (!isExposed())
+        return;
+
+    context->makeCurrent(this);
+    Core::System::GetInstance().Renderer().TryPresent(100);
+    context->swapBuffers(this);
+    auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
+    f->glFinish();
+    QWindow::requestUpdate();
+}
+
+bool OpenGLWindow::event(QEvent* event) {
+    switch (event->type()) {
+    case QEvent::UpdateRequest:
+        Present();
+        return true;
+    case QEvent::MouseButtonPress:
+    case QEvent::MouseButtonRelease:
+    case QEvent::MouseButtonDblClick:
+    case QEvent::MouseMove:
+    case QEvent::KeyPress:
+    case QEvent::KeyRelease:
+    case QEvent::FocusIn:
+    case QEvent::FocusOut:
+    case QEvent::FocusAboutToChange:
+    case QEvent::Enter:
+    case QEvent::Leave:
+    case QEvent::Wheel:
+    case QEvent::TabletMove:
+    case QEvent::TabletPress:
+    case QEvent::TabletRelease:
+    case QEvent::TabletEnterProximity:
+    case QEvent::TabletLeaveProximity:
+    case QEvent::TouchBegin:
+    case QEvent::TouchUpdate:
+    case QEvent::TouchEnd:
+    case QEvent::InputMethodQuery:
+    case QEvent::TouchCancel:
+        return QCoreApplication::sendEvent(event_handler, event);
+    case QEvent::Drop:
+        GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
+        return true;
+    case QEvent::DragResponse:
+    case QEvent::DragEnter:
+    case QEvent::DragLeave:
+    case QEvent::DragMove:
+        GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
+        return true;
+    default:
+        return QWindow::event(event);
     }
-};
+}
 
-#ifdef HAS_VULKAN
-class GVKWidgetInternal final : public GWidgetInternal {
-public:
-    GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
-        setSurfaceType(QSurface::SurfaceType::VulkanSurface);
-        setVulkanInstance(instance);
-    }
-    ~GVKWidgetInternal() override = default;
-};
-#endif
+void OpenGLWindow::exposeEvent(QExposeEvent* event) {
+    QWindow::requestUpdate();
+    QWindow::exposeEvent(event);
+}
 
-GRenderWindow::GRenderWindow(GMainWindow* 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("yuzu %1 | %2-%3")
                        .arg(QString::fromUtf8(Common::g_build_name),
                             QString::fromUtf8(Common::g_scm_branch),
                             QString::fromUtf8(Common::g_scm_desc)));
     setAttribute(Qt::WA_AcceptTouchEvents);
-
+    auto layout = new QHBoxLayout(this);
+    layout->setMargin(0);
+    setLayout(layout);
     InputCommon::Init();
+
+    GMainWindow* parent = GetMainWindow();
     connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
 }
 
 GRenderWindow::~GRenderWindow() {
     InputCommon::Shutdown();
-
-    // Avoid an unordered destruction that generates a segfault
-    delete child;
 }
 
-void GRenderWindow::moveContext() {
-    if (!context) {
-        return;
-    }
-    DoneCurrent();
-
-    // If the thread started running, move the GL Context to the new thread. Otherwise, move it
-    // back.
-    auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
-                      ? emu_thread
-                      : qApp->thread();
-    context->moveToThread(thread);
+void GRenderWindow::MakeCurrent() {
+    core_context->MakeCurrent();
 }
 
-void GRenderWindow::SwapBuffers() {
-    if (context) {
-        context->swapBuffers(child);
-    }
+void GRenderWindow::DoneCurrent() {
+    core_context->DoneCurrent();
+}
+
+void GRenderWindow::PollEvents() {
     if (!first_frame) {
         first_frame = true;
         emit FirstFrameDisplayed();
     }
 }
 
-void GRenderWindow::MakeCurrent() {
-    if (context) {
-        context->makeCurrent(child);
-    }
-}
-
-void GRenderWindow::DoneCurrent() {
-    if (context) {
-        context->doneCurrent();
-    }
-}
-
-void GRenderWindow::PollEvents() {}
-
 bool GRenderWindow::IsShown() const {
     return !isMinimized();
 }
@@ -309,21 +292,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i
 void GRenderWindow::OnFramebufferSizeChanged() {
     // Screen changes potentially incur a change in screen DPI, hence we should update the
     // framebuffer size
-    const qreal pixelRatio{GetWindowPixelRatio()};
-    const auto size{child->GetSize()};
-    UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
-}
-
-void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
-    if (child) {
-        child->keyPressEvent(event);
-    }
-}
-
-void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
-    if (child) {
-        child->keyReleaseEvent(event);
-    }
+    const qreal pixel_ratio = windowPixelRatio();
+    const u32 width = this->width() * pixel_ratio;
+    const u32 height = this->height() * pixel_ratio;
+    UpdateCurrentFramebufferLayout(width, height);
 }
 
 void GRenderWindow::BackupGeometry() {
@@ -351,13 +323,12 @@ QByteArray GRenderWindow::saveGeometry() {
     return geometry;
 }
 
-qreal GRenderWindow::GetWindowPixelRatio() const {
-    // windowHandle() might not be accessible until the window is displayed to screen.
-    return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
+qreal GRenderWindow::windowPixelRatio() const {
+    return devicePixelRatio();
 }
 
 std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
-    const qreal pixel_ratio{GetWindowPixelRatio()};
+    const qreal pixel_ratio = windowPixelRatio();
     return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
             static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
 }
@@ -367,6 +338,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
     QWidget::closeEvent(event);
 }
 
+void GRenderWindow::keyPressEvent(QKeyEvent* event) {
+    InputCommon::GetKeyboard()->PressKey(event->key());
+}
+
+void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
+    InputCommon::GetKeyboard()->ReleaseKey(event->key());
+}
+
+void GRenderWindow::mousePressEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchBeginEvent
+
+    auto pos = event->pos();
+    if (event->button() == Qt::LeftButton) {
+        const auto [x, y] = ScaleTouch(pos);
+        this->TouchPressed(x, y);
+    } else if (event->button() == Qt::RightButton) {
+        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+    }
+}
+
+void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchUpdateEvent
+
+    auto pos = event->pos();
+    const auto [x, y] = ScaleTouch(pos);
+    this->TouchMoved(x, y);
+    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+}
+
+void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchEndEvent
+
+    if (event->button() == Qt::LeftButton)
+        this->TouchReleased();
+    else if (event->button() == Qt::RightButton)
+        InputCommon::GetMotionEmu()->EndTilt();
+}
+
 void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
     // TouchBegin always has exactly one touch point, so take the .first()
     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
@@ -415,26 +427,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
     InputCommon::GetKeyboard()->ReleaseAllKeys();
 }
 
-void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {
-    NotifyClientAreaSizeChanged(std::make_pair(width, height));
+void GRenderWindow::resizeEvent(QResizeEvent* event) {
+    QWidget::resizeEvent(event);
+    OnFramebufferSizeChanged();
 }
 
 std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
-    return std::make_unique<GGLContext>(context.get());
+    if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
+        return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext());
+    }
+    return {};
 }
 
 bool GRenderWindow::InitRenderTarget() {
-    shared_context.reset();
-    context.reset();
-    if (child) {
-        delete child;
-    }
-    if (container) {
-        delete container;
-    }
-    if (layout()) {
-        delete layout();
-    }
+    ReleaseRenderTarget();
 
     first_frame = false;
 
@@ -451,13 +457,6 @@ bool GRenderWindow::InitRenderTarget() {
         break;
     }
 
-    container = QWidget::createWindowContainer(child, this);
-    QBoxLayout* layout = new QHBoxLayout(this);
-
-    layout->addWidget(container);
-    layout->setMargin(0);
-    setLayout(layout);
-
     // Reset minimum required size to avoid resizing issues on the main window after restarting.
     setMinimumSize(1, 1);
 
@@ -467,14 +466,9 @@ bool GRenderWindow::InitRenderTarget() {
     hide();
 
     resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
-    child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
-    container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
 
     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
-
     OnFramebufferSizeChanged();
-    NotifyClientAreaSizeChanged(child->GetSize());
-
     BackupGeometry();
 
     if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
@@ -486,6 +480,14 @@ bool GRenderWindow::InitRenderTarget() {
     return true;
 }
 
+void GRenderWindow::ReleaseRenderTarget() {
+    if (child_widget) {
+        layout()->removeWidget(child_widget);
+        delete child_widget;
+        child_widget = nullptr;
+    }
+}
+
 void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
     auto& renderer = Core::System::GetInstance().Renderer();
 
@@ -521,16 +523,20 @@ bool GRenderWindow::InitializeOpenGL() {
     fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
     // TODO: expose a setting for buffer value (ie default/single/double/triple)
     fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
-    shared_context = std::make_unique<QOpenGLContext>();
-    shared_context->setFormat(fmt);
-    shared_context->create();
-    context = std::make_unique<QOpenGLContext>();
-    context->setShareContext(shared_context.get());
-    context->setFormat(fmt);
-    context->create();
-    fmt.setSwapInterval(false);
+    fmt.setSwapInterval(0);
+    QSurfaceFormat::setDefaultFormat(fmt);
+
+    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(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+    layout()->addWidget(child_widget);
+
+    core_context = CreateSharedContext();
+    resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
 
-    child = new GGLWidgetInternal(this, shared_context.get());
     return true;
 }
 
@@ -621,12 +627,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
 
 void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
     this->emu_thread = emu_thread;
-    child->DisablePainting();
 }
 
 void GRenderWindow::OnEmulationStopping() {
     emu_thread = nullptr;
-    child->EnablePainting();
 }
 
 void GRenderWindow::showEvent(QShowEvent* event) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 71a2fa3213..37bc4f0437 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -11,6 +11,7 @@
 #include <QImage>
 #include <QThread>
 #include <QWidget>
+#include <QWindow>
 
 #include "common/thread.h"
 #include "core/core.h"
@@ -42,7 +43,7 @@ class EmuThread final : public QThread {
     Q_OBJECT
 
 public:
-    explicit EmuThread(GRenderWindow* render_window);
+    explicit EmuThread(Core::Frontend::GraphicsContext& context);
     ~EmuThread() override;
 
     /**
@@ -96,7 +97,7 @@ private:
     std::mutex running_mutex;
     std::condition_variable running_cv;
 
-    GRenderWindow* render_window;
+    Core::Frontend::GraphicsContext& core_context;
 
 signals:
     /**
@@ -122,15 +123,32 @@ signals:
     void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
 };
 
+class OpenGLWindow : public QWindow {
+    Q_OBJECT
+public:
+    explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
+
+    ~OpenGLWindow();
+
+    void Present();
+
+protected:
+    bool event(QEvent* event) override;
+    void exposeEvent(QExposeEvent* event) override;
+
+private:
+    QOpenGLContext* context;
+    QWidget* event_handler;
+};
+
 class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
     Q_OBJECT
 
 public:
-    GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);
+    GRenderWindow(QWidget* parent, EmuThread* emu_thread);
     ~GRenderWindow() override;
 
-    // EmuWindow implementation
-    void SwapBuffers() override;
+    // EmuWindow implementation.
     void MakeCurrent() override;
     void DoneCurrent() override;
     void PollEvents() override;
@@ -139,30 +157,36 @@ public:
                                 void* surface) const override;
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
-    void ForwardKeyPressEvent(QKeyEvent* event);
-    void ForwardKeyReleaseEvent(QKeyEvent* event);
-
     void BackupGeometry();
     void RestoreGeometry();
     void restoreGeometry(const QByteArray& geometry); // overridden
     QByteArray saveGeometry();                        // overridden
 
-    qreal GetWindowPixelRatio() const;
-    std::pair<u32, u32> ScaleTouch(QPointF pos) const;
+    qreal windowPixelRatio() const;
 
     void closeEvent(QCloseEvent* event) override;
+
+    void resizeEvent(QResizeEvent* event) override;
+
+    void keyPressEvent(QKeyEvent* event) override;
+    void keyReleaseEvent(QKeyEvent* event) override;
+
+    void mousePressEvent(QMouseEvent* event) override;
+    void mouseMoveEvent(QMouseEvent* event) override;
+    void mouseReleaseEvent(QMouseEvent* event) override;
+
     bool event(QEvent* event) override;
+
     void focusOutEvent(QFocusEvent* event) override;
 
-    void OnClientAreaResized(u32 width, u32 height);
-
     bool InitRenderTarget();
 
+    /// Destroy the previous run's child_widget which should also destroy the child_window
+    void ReleaseRenderTarget();
+
     void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
 
 public slots:
-    void moveContext(); // overridden
-
     void OnEmulationStarting(EmuThread* emu_thread);
     void OnEmulationStopping();
     void OnFramebufferSizeChanged();
@@ -173,6 +197,7 @@ signals:
     void FirstFrameDisplayed();
 
 private:
+    std::pair<u32, u32> ScaleTouch(QPointF pos) const;
     void TouchBeginEvent(const QTouchEvent* event);
     void TouchUpdateEvent(const QTouchEvent* event);
     void TouchEndEvent();
@@ -184,15 +209,9 @@ private:
     bool LoadOpenGL();
     QStringList GetUnsupportedGLExtensions() const;
 
-    QWidget* container = nullptr;
-    GWidgetInternal* child = nullptr;
-
     EmuThread* emu_thread;
-    // Context that backs the GGLWidgetInternal (and will be used by core to render)
-    std::unique_ptr<QOpenGLContext> context;
-    // Context that will be shared between all newly created contexts. This should never be made
-    // current
-    std::unique_ptr<QOpenGLContext> shared_context;
+
+    std::unique_ptr<GraphicsContext> core_context;
 
 #ifdef HAS_VULKAN
     std::unique_ptr<QVulkanInstance> vk_instance;
@@ -202,6 +221,15 @@ private:
     QImage screenshot_image;
 
     QByteArray geometry;
+
+    /// Native window handle that backs this presentation widget
+    QWindow* child_window = nullptr;
+
+    /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
+    /// put the child_window into a widget then add it to the layout. This child_widget can be
+    /// parented to GRenderWindow and use Qt's lifetime system
+    QWidget* child_widget = nullptr;
+
     bool first_frame = false;
 
 protected: