From c6a0ab9792390fefa816ace846e3a76a85e8b4d5 Mon Sep 17 00:00:00 2001 From: James Rowe 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 7006a37b3..75c2be4ae 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 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 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 f74cb693a..087ee8f93 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 #include #include +#include +#include +#include #include #include #include @@ -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(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 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 GRenderWindow::CreateSharedContext() const { + return std::make_unique(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(); + shared_context->setFormat(fmt); + shared_context->create(); + context = std::make_unique(); + 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(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 d1f37e503..d2a440d0d 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #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 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 context; + // Context that will be shared between all newly created contexts. This should never be made + // current + std::unique_ptr shared_context; /// Temporary storage of the screenshot taken QImage screenshot_image; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2c3e27c2e..c8db7b907 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