From c6a0ab9792390fefa816ace846e3a76a85e8b4d5 Mon Sep 17 00:00:00 2001
From: James Rowe <jroweboy@gmail.com>
Date: Fri, 11 Jan 2019 21:06:34 -0700
Subject: [PATCH] QT Frontend: Migrate to QOpenGLWindow

---
 src/core/frontend/emu_window.h | 39 +++++++++++----
 src/yuzu/bootmanager.cpp       | 91 +++++++++++++++++++++++++++-------
 src/yuzu/bootmanager.h         | 10 +++-
 src/yuzu/main.cpp              |  3 +-
 4 files changed, 113 insertions(+), 30 deletions(-)

diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 7006a37b3e..75c2be4aec 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -12,6 +12,23 @@
 
 namespace Core::Frontend {
 
+/**
+ * Represents a graphics context that can be used for background computation or drawing. If the
+ * graphics backend doesn't require the context, then the implementation of these methods can be
+ * stubs
+ */
+class GraphicsContext {
+public:
+    /// Makes the graphics context current for the caller thread
+    virtual void MakeCurrent() = 0;
+
+    /// Releases (dunno if this is the "right" word) the context from the caller thread
+    virtual void DoneCurrent() = 0;
+
+    /// Swap buffers to display the next frame
+    virtual void SwapBuffers() = 0;
+};
+
 /**
  * Abstraction class used to provide an interface between emulation code and the frontend
  * (e.g. SDL, QGLWidget, GLFW, etc...).
@@ -30,7 +47,7 @@ namespace Core::Frontend {
  * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
  *   re-read the upper points again and think about it if you don't see this.
  */
-class EmuWindow {
+class EmuWindow : public GraphicsContext {
 public:
     /// Data structure to store emuwindow configuration
     struct WindowConfig {
@@ -40,17 +57,21 @@ public:
         std::pair<unsigned, unsigned> min_client_area_size;
     };
 
-    /// Swap buffers to display the next frame
-    virtual void SwapBuffers() = 0;
-
     /// Polls window events
     virtual void PollEvents() = 0;
 
-    /// Makes the graphics context current for the caller thread
-    virtual void MakeCurrent() = 0;
-
-    /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
-    virtual void DoneCurrent() = 0;
+    /**
+     * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This
+     * context can be used from other threads for background graphics computation. If the frontend
+     * is using a graphics backend that doesn't need anything specific to run on a different thread,
+     * then it can use a stubbed implemenation for GraphicsContext.
+     *
+     * If the return value is null, then the core should assume that the frontend cannot provide a
+     * Shared Context
+     */
+    virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const {
+        return nullptr;
+    }
 
     /**
      * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index f74cb693a4..087ee8f93b 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,6 +1,13 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
 #include <QApplication>
 #include <QHBoxLayout>
 #include <QKeyEvent>
+#include <QOffscreenSurface>
+#include <QOpenGLWindow>
+#include <QPainter>
 #include <QScreen>
 #include <QWindow>
 #include <fmt/format.h>
@@ -73,13 +80,36 @@ void EmuThread::run() {
     render_window->moveContext();
 }
 
+class GGLContext : public Core::Frontend::GraphicsContext {
+public:
+    explicit GGLContext(QOpenGLContext* shared_context) : surface() {
+        context = std::make_unique<QOpenGLContext>(shared_context);
+        surface.setFormat(shared_context->format());
+        surface.create();
+    }
+
+    void MakeCurrent() override {
+        context->makeCurrent(&surface);
+    }
+
+    void DoneCurrent() override {
+        context->doneCurrent();
+    }
+
+    void SwapBuffers() override {}
+
+private:
+    std::unique_ptr<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 : public QGLWidget {
+class GGLWidgetInternal : public QOpenGLWindow {
 public:
-    GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
-        : QGLWidget(fmt, parent), parent(parent) {}
+    GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
+        : QOpenGLWindow(shared_context), parent(parent) {}
 
     void paintEvent(QPaintEvent* ev) override {
         if (do_painting) {
@@ -105,7 +135,7 @@ private:
 };
 
 GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
-    : QWidget(parent), child(nullptr), emu_thread(emu_thread) {
+    : QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) {
 
     setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
                        .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
@@ -129,19 +159,19 @@ void GRenderWindow::moveContext() {
     auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
                       ? emu_thread
                       : qApp->thread();
-    child->context()->moveToThread(thread);
+    context->moveToThread(thread);
 }
 
 void GRenderWindow::SwapBuffers() {
-    // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
+    // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,
     // since we never call `doneCurrent` in this thread.
     // However:
     // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
     // since the last time `swapBuffers` was executed;
     // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
-    child->makeCurrent();
+    context->makeCurrent(child);
 
-    child->swapBuffers();
+    context->swapBuffers(child);
     if (!first_frame) {
         emit FirstFrameDisplayed();
         first_frame = true;
@@ -149,11 +179,11 @@ void GRenderWindow::SwapBuffers() {
 }
 
 void GRenderWindow::MakeCurrent() {
-    child->makeCurrent();
+    context->makeCurrent(child);
 }
 
 void GRenderWindow::DoneCurrent() {
-    child->doneCurrent();
+    context->doneCurrent();
 }
 
 void GRenderWindow::PollEvents() {}
@@ -173,7 +203,7 @@ void GRenderWindow::OnFramebufferSizeChanged() {
 }
 
 void GRenderWindow::BackupGeometry() {
-    geometry = ((QGLWidget*)this)->saveGeometry();
+    geometry = ((QWidget*)this)->saveGeometry();
 }
 
 void GRenderWindow::RestoreGeometry() {
@@ -191,7 +221,7 @@ QByteArray GRenderWindow::saveGeometry() {
     // If we are a top-level widget, store the current geometry
     // otherwise, store the last backup
     if (parent() == nullptr)
-        return ((QGLWidget*)this)->saveGeometry();
+        return ((QWidget*)this)->saveGeometry();
     else
         return geometry;
 }
@@ -305,7 +335,19 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
     NotifyClientAreaSizeChanged(std::make_pair(width, height));
 }
 
+std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
+    return std::make_unique<GGLContext>(shared_context.get());
+}
+
 void GRenderWindow::InitRenderTarget() {
+    if (shared_context) {
+        shared_context.reset();
+    }
+
+    if (context) {
+        context.reset();
+    }
+
     if (child) {
         delete child;
     }
@@ -318,19 +360,28 @@ void GRenderWindow::InitRenderTarget() {
 
     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
     // WA_DontShowOnScreen, WA_DeleteOnClose
-    QGLFormat fmt;
+    QSurfaceFormat fmt;
     fmt.setVersion(4, 3);
-    fmt.setProfile(QGLFormat::CoreProfile);
+    fmt.setProfile(QSurfaceFormat::CoreProfile);
+    // 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);
 
-    // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
-    fmt.setOption(QGL::NoDeprecatedFunctions);
-
-    child = new GGLWidgetInternal(fmt, this);
+    child = new GGLWidgetInternal(this, shared_context.get());
+    QWidget* container = QWidget::createWindowContainer(child, this);
     QBoxLayout* layout = new QHBoxLayout(this);
 
     resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
-    layout->addWidget(child);
+    child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+    container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+    layout->addWidget(container);
     layout->setMargin(0);
     setLayout(layout);
 
@@ -340,6 +391,8 @@ void GRenderWindow::InitRenderTarget() {
     NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height()));
 
     BackupGeometry();
+    // show causes the window to actually be created and the gl context as well
+    show();
 }
 
 void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index d1f37e5032..d2a440d0d2 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,9 +7,9 @@
 #include <atomic>
 #include <condition_variable>
 #include <mutex>
-#include <QGLWidget>
 #include <QImage>
 #include <QThread>
+#include <QWidget>
 #include "common/thread.h"
 #include "core/core.h"
 #include "core/frontend/emu_window.h"
@@ -21,6 +21,8 @@ class QTouchEvent;
 class GGLWidgetInternal;
 class GMainWindow;
 class GRenderWindow;
+class QSurface;
+class QOpenGLContext;
 
 class EmuThread : public QThread {
     Q_OBJECT
@@ -115,6 +117,7 @@ public:
     void MakeCurrent() override;
     void DoneCurrent() override;
     void PollEvents() override;
+    std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
     void BackupGeometry();
     void RestoreGeometry();
@@ -168,6 +171,11 @@ private:
     QByteArray geometry;
 
     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;
 
     /// Temporary storage of the screenshot taken
     QImage screenshot_image;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2c3e27c2e7..c8db7b907a 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2032,7 +2032,8 @@ int main(int argc, char* argv[]) {
     QCoreApplication::setOrganizationName("yuzu team");
     QCoreApplication::setApplicationName("yuzu");
 
-    QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
+    // Enables the core to make the qt created contexts current on std::threads
+    QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
     QApplication app(argc, argv);
 
     // Qt changes the locale and causes issues in float conversion using std::to_string() when
-- 
GitLab