yuzu-emu
/
yuzu-mainline
Archived
1
0
Fork 0

QT Frontend: Migrate to QOpenGLWindow

This commit is contained in:
James Rowe 2019-01-11 21:06:34 -07:00
parent 125599c2d5
commit c6a0ab9792
4 changed files with 113 additions and 30 deletions

View File

@ -12,6 +12,23 @@
namespace Core::Frontend { 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 * Abstraction class used to provide an interface between emulation code and the frontend
* (e.g. SDL, QGLWidget, GLFW, etc...). * (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 * - 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. * re-read the upper points again and think about it if you don't see this.
*/ */
class EmuWindow { class EmuWindow : public GraphicsContext {
public: public:
/// Data structure to store emuwindow configuration /// Data structure to store emuwindow configuration
struct WindowConfig { struct WindowConfig {
@ -40,17 +57,21 @@ public:
std::pair<unsigned, unsigned> min_client_area_size; std::pair<unsigned, unsigned> min_client_area_size;
}; };
/// Swap buffers to display the next frame
virtual void SwapBuffers() = 0;
/// Polls window events /// Polls window events
virtual void PollEvents() = 0; virtual void PollEvents() = 0;
/// Makes the graphics context current for the caller thread /**
virtual void MakeCurrent() = 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
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread * is using a graphics backend that doesn't need anything specific to run on a different thread,
virtual void DoneCurrent() = 0; * 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) * Signal that a touch pressed event has occurred (e.g. mouse click pressed)

View File

@ -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 <QApplication>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QKeyEvent> #include <QKeyEvent>
#include <QOffscreenSurface>
#include <QOpenGLWindow>
#include <QPainter>
#include <QScreen> #include <QScreen>
#include <QWindow> #include <QWindow>
#include <fmt/format.h> #include <fmt/format.h>
@ -73,13 +80,36 @@ void EmuThread::run() {
render_window->moveContext(); 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 // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
// context. // context.
// The corresponding functionality is handled in EmuThread instead // The corresponding functionality is handled in EmuThread instead
class GGLWidgetInternal : public QGLWidget { class GGLWidgetInternal : public QOpenGLWindow {
public: public:
GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
: QGLWidget(fmt, parent), parent(parent) {} : QOpenGLWindow(shared_context), parent(parent) {}
void paintEvent(QPaintEvent* ev) override { void paintEvent(QPaintEvent* ev) override {
if (do_painting) { if (do_painting) {
@ -105,7 +135,7 @@ private:
}; };
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) 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") setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); .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) auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
? emu_thread ? emu_thread
: qApp->thread(); : qApp->thread();
child->context()->moveToThread(thread); context->moveToThread(thread);
} }
void GRenderWindow::SwapBuffers() { 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. // since we never call `doneCurrent` in this thread.
// However: // However:
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
// since the last time `swapBuffers` was executed; // since the last time `swapBuffers` was executed;
// - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. // - 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) { if (!first_frame) {
emit FirstFrameDisplayed(); emit FirstFrameDisplayed();
first_frame = true; first_frame = true;
@ -149,11 +179,11 @@ void GRenderWindow::SwapBuffers() {
} }
void GRenderWindow::MakeCurrent() { void GRenderWindow::MakeCurrent() {
child->makeCurrent(); context->makeCurrent(child);
} }
void GRenderWindow::DoneCurrent() { void GRenderWindow::DoneCurrent() {
child->doneCurrent(); context->doneCurrent();
} }
void GRenderWindow::PollEvents() {} void GRenderWindow::PollEvents() {}
@ -173,7 +203,7 @@ void GRenderWindow::OnFramebufferSizeChanged() {
} }
void GRenderWindow::BackupGeometry() { void GRenderWindow::BackupGeometry() {
geometry = ((QGLWidget*)this)->saveGeometry(); geometry = ((QWidget*)this)->saveGeometry();
} }
void GRenderWindow::RestoreGeometry() { void GRenderWindow::RestoreGeometry() {
@ -191,7 +221,7 @@ QByteArray GRenderWindow::saveGeometry() {
// If we are a top-level widget, store the current geometry // If we are a top-level widget, store the current geometry
// otherwise, store the last backup // otherwise, store the last backup
if (parent() == nullptr) if (parent() == nullptr)
return ((QGLWidget*)this)->saveGeometry(); return ((QWidget*)this)->saveGeometry();
else else
return geometry; return geometry;
} }
@ -305,7 +335,19 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
NotifyClientAreaSizeChanged(std::make_pair(width, 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() { void GRenderWindow::InitRenderTarget() {
if (shared_context) {
shared_context.reset();
}
if (context) {
context.reset();
}
if (child) { if (child) {
delete child; delete child;
} }
@ -318,19 +360,28 @@ void GRenderWindow::InitRenderTarget() {
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose // WA_DontShowOnScreen, WA_DeleteOnClose
QGLFormat fmt; QSurfaceFormat fmt;
fmt.setVersion(4, 3); 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); fmt.setSwapInterval(false);
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X child = new GGLWidgetInternal(this, shared_context.get());
fmt.setOption(QGL::NoDeprecatedFunctions); QWidget* container = QWidget::createWindowContainer(child, this);
child = new GGLWidgetInternal(fmt, this);
QBoxLayout* layout = new QHBoxLayout(this); QBoxLayout* layout = new QHBoxLayout(this);
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); 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); layout->setMargin(0);
setLayout(layout); setLayout(layout);
@ -340,6 +391,8 @@ void GRenderWindow::InitRenderTarget() {
NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height()));
BackupGeometry(); 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) { void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {

View File

@ -7,9 +7,9 @@
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <QGLWidget>
#include <QImage> #include <QImage>
#include <QThread> #include <QThread>
#include <QWidget>
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
@ -21,6 +21,8 @@ class QTouchEvent;
class GGLWidgetInternal; class GGLWidgetInternal;
class GMainWindow; class GMainWindow;
class GRenderWindow; class GRenderWindow;
class QSurface;
class QOpenGLContext;
class EmuThread : public QThread { class EmuThread : public QThread {
Q_OBJECT Q_OBJECT
@ -115,6 +117,7 @@ public:
void MakeCurrent() override; void MakeCurrent() override;
void DoneCurrent() override; void DoneCurrent() override;
void PollEvents() override; void PollEvents() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
void BackupGeometry(); void BackupGeometry();
void RestoreGeometry(); void RestoreGeometry();
@ -168,6 +171,11 @@ private:
QByteArray geometry; QByteArray geometry;
EmuThread* emu_thread; 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 /// Temporary storage of the screenshot taken
QImage screenshot_image; QImage screenshot_image;

View File

@ -2032,7 +2032,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName("yuzu team"); QCoreApplication::setOrganizationName("yuzu team");
QCoreApplication::setApplicationName("yuzu"); 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); QApplication app(argc, argv);
// Qt changes the locale and causes issues in float conversion using std::to_string() when // Qt changes the locale and causes issues in float conversion using std::to_string() when