mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-12-23 09:53:10 -06:00
QT Frontend: Migrate to QOpenGLWindow
This commit is contained in:
parent
125599c2d5
commit
c6a0ab9792
4 changed files with 113 additions and 30 deletions
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue