From c6a0ab9792390fefa816ace846e3a76a85e8b4d5 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Fri, 11 Jan 2019 21:06:34 -0700 Subject: QT Frontend: Migrate to QOpenGLWindow --- src/yuzu/bootmanager.cpp | 91 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 19 deletions(-) (limited to 'src/yuzu/bootmanager.cpp') 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) { -- cgit v1.2.3 From 5f2d9f282aa31c9658f044e7095d3ecbe12105eb Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 21 Jan 2019 16:21:44 -0700 Subject: QT: Hide GLWidget immediately after showing. With the loading screen merged, we don't want to actually show at this point, but it still needs to be shown to actually create the context. Turns out you can just show and hide it immediately and it'll work. --- src/yuzu/bootmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/yuzu/bootmanager.cpp') diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 087ee8f93..ea55e68ba 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -393,6 +393,8 @@ void GRenderWindow::InitRenderTarget() { BackupGeometry(); // show causes the window to actually be created and the gl context as well show(); + // but we don't want the widget to be shown yet, so immediately hide it + hide(); } void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { -- cgit v1.2.3 From 9ff72ca9f2147ff41101d60fb806357825d5aa53 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Mon, 25 Mar 2019 16:41:48 -0300 Subject: bootmanager: Delete container to avoid crash on game restarting While we are at it, remove nullptr checks for deletion, since the C++ standard defines that delete does it by its own --- src/yuzu/bootmanager.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'src/yuzu/bootmanager.cpp') diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index ea55e68ba..8445d9fe3 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -340,21 +340,16 @@ std::unique_ptr GRenderWindow::CreateSharedCont } void GRenderWindow::InitRenderTarget() { - if (shared_context) { - shared_context.reset(); - } + shared_context.reset(); + context.reset(); - if (context) { - context.reset(); - } + delete child; + child = nullptr; - if (child) { - delete child; - } + delete container; + container = nullptr; - if (layout()) { - delete layout(); - } + delete layout(); first_frame = false; @@ -375,7 +370,7 @@ void GRenderWindow::InitRenderTarget() { fmt.setSwapInterval(false); child = new GGLWidgetInternal(this, shared_context.get()); - QWidget* container = QWidget::createWindowContainer(child, this); + container = QWidget::createWindowContainer(child, this); QBoxLayout* layout = new QHBoxLayout(this); resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); -- cgit v1.2.3 From bbb396d7f1e4bd28fdd921af91b766d4504e5f4a Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Mon, 25 Mar 2019 16:46:30 -0300 Subject: bootmanager: Bypass resizing issue --- src/yuzu/bootmanager.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'src/yuzu/bootmanager.cpp') diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 8445d9fe3..8b4e59eb3 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -371,14 +371,23 @@ void GRenderWindow::InitRenderTarget() { child = new GGLWidgetInternal(this, shared_context.get()); container = QWidget::createWindowContainer(child, this); + QBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(container); + layout->setMargin(0); + setLayout(layout); + + // Reset minimum size to avoid unwanted resizes when this function is called for a second time. + setMinimumSize(1, 1); + + // Show causes the window to actually be created and the OpenGL context as well, but we don't + // want the widget to be shown yet, so immediately hide it. + show(); + hide(); resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); - layout->addWidget(container); - layout->setMargin(0); - setLayout(layout); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); @@ -386,10 +395,6 @@ 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(); - // but we don't want the widget to be shown yet, so immediately hide it - hide(); } void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { -- cgit v1.2.3 From 9ebc27234d1f79516ea1a785c31551c26118997d Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Mon, 25 Mar 2019 17:01:11 -0300 Subject: bootmanager: Bypass input focus issues --- src/yuzu/bootmanager.cpp | 101 ++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 44 deletions(-) (limited to 'src/yuzu/bootmanager.cpp') diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 8b4e59eb3..e1825e607 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -122,9 +122,51 @@ public: 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; } @@ -196,12 +238,24 @@ void GRenderWindow::PollEvents() {} void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the // framebuffer size - qreal pixelRatio = windowPixelRatio(); + qreal pixelRatio = GetWindowPixelRatio(); unsigned width = child->QPaintDevice::width() * pixelRatio; unsigned height = child->QPaintDevice::height() * pixelRatio; UpdateCurrentFramebufferLayout(width, height); } +void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { + if (child) { + child->keyPressEvent(event); + } +} + +void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { + if (child) { + child->keyReleaseEvent(event); + } +} + void GRenderWindow::BackupGeometry() { geometry = ((QWidget*)this)->saveGeometry(); } @@ -226,13 +280,13 @@ QByteArray GRenderWindow::saveGeometry() { return geometry; } -qreal GRenderWindow::windowPixelRatio() const { +qreal GRenderWindow::GetWindowPixelRatio() const { // windowHandle() might not be accessible until the window is displayed to screen. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; } std::pair GRenderWindow::ScaleTouch(const QPointF pos) const { - const qreal pixel_ratio = windowPixelRatio(); + const qreal pixel_ratio = GetWindowPixelRatio(); return {static_cast(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), static_cast(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; } @@ -242,47 +296,6 @@ 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()); -- cgit v1.2.3