Merge pull request #2017 from jroweboy/glwidget
Frontend: Migrate to QOpenGLWindow and support shared contexts
This commit is contained in:
		| @@ -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) | ||||||
|   | |||||||
| @@ -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> | ||||||
| @@ -82,13 +89,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) { | ||||||
| @@ -101,9 +131,51 @@ public: | |||||||
|         parent->OnFramebufferSizeChanged(); |         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() { |     void DisablePainting() { | ||||||
|         do_painting = false; |         do_painting = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void EnablePainting() { |     void EnablePainting() { | ||||||
|         do_painting = true; |         do_painting = true; | ||||||
|     } |     } | ||||||
| @@ -114,7 +186,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)); | ||||||
| @@ -137,19 +209,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; | ||||||
| @@ -157,11 +229,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() {} | ||||||
| @@ -174,14 +246,26 @@ void GRenderWindow::PollEvents() {} | |||||||
| void GRenderWindow::OnFramebufferSizeChanged() { | void GRenderWindow::OnFramebufferSizeChanged() { | ||||||
|     // Screen changes potentially incur a change in screen DPI, hence we should update the |     // Screen changes potentially incur a change in screen DPI, hence we should update the | ||||||
|     // framebuffer size |     // framebuffer size | ||||||
|     qreal pixelRatio = windowPixelRatio(); |     qreal pixelRatio = GetWindowPixelRatio(); | ||||||
|     unsigned width = child->QPaintDevice::width() * pixelRatio; |     unsigned width = child->QPaintDevice::width() * pixelRatio; | ||||||
|     unsigned height = child->QPaintDevice::height() * pixelRatio; |     unsigned height = child->QPaintDevice::height() * pixelRatio; | ||||||
|     UpdateCurrentFramebufferLayout(width, height); |     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() { | void GRenderWindow::BackupGeometry() { | ||||||
|     geometry = ((QGLWidget*)this)->saveGeometry(); |     geometry = ((QWidget*)this)->saveGeometry(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void GRenderWindow::RestoreGeometry() { | void GRenderWindow::RestoreGeometry() { | ||||||
| @@ -199,18 +283,18 @@ 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; | ||||||
| } | } | ||||||
|  |  | ||||||
| qreal GRenderWindow::windowPixelRatio() const { | qreal GRenderWindow::GetWindowPixelRatio() const { | ||||||
|     // windowHandle() might not be accessible until the window is displayed to screen. |     // windowHandle() might not be accessible until the window is displayed to screen. | ||||||
|     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; |     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { | std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { | ||||||
|     const qreal pixel_ratio = windowPixelRatio(); |     const qreal pixel_ratio = GetWindowPixelRatio(); | ||||||
|     return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), |     return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), | ||||||
|             static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; |             static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; | ||||||
| } | } | ||||||
| @@ -220,47 +304,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { | |||||||
|     QWidget::closeEvent(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) { | void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { | ||||||
|     // TouchBegin always has exactly one touch point, so take the .first() |     // TouchBegin always has exactly one touch point, so take the .first() | ||||||
|     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); |     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); | ||||||
| @@ -313,35 +356,60 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { | |||||||
|     NotifyClientAreaSizeChanged(std::make_pair(width, height)); |     NotifyClientAreaSizeChanged(std::make_pair(width, height)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void GRenderWindow::InitRenderTarget() { | std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||||||
|     if (child) { |     return std::make_unique<GGLContext>(shared_context.get()); | ||||||
|         delete child; | } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (layout()) { | void GRenderWindow::InitRenderTarget() { | ||||||
|         delete layout(); |     shared_context.reset(); | ||||||
|     } |     context.reset(); | ||||||
|  |  | ||||||
|  |     delete child; | ||||||
|  |     child = nullptr; | ||||||
|  |  | ||||||
|  |     delete container; | ||||||
|  |     container = nullptr; | ||||||
|  |  | ||||||
|  |     delete layout(); | ||||||
|  |  | ||||||
|     first_frame = false; |     first_frame = false; | ||||||
|  |  | ||||||
|     // 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); |     container = QWidget::createWindowContainer(child, this); | ||||||
|  |  | ||||||
|     child = new GGLWidgetInternal(fmt, this); |  | ||||||
|     QBoxLayout* layout = new QHBoxLayout(this); |     QBoxLayout* layout = new QHBoxLayout(this); | ||||||
|  |     layout->addWidget(container); | ||||||
|     resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); |  | ||||||
|     layout->addWidget(child); |  | ||||||
|     layout->setMargin(0); |     layout->setMargin(0); | ||||||
|     setLayout(layout); |     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); | ||||||
|  |  | ||||||
|     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); |     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | ||||||
|  |  | ||||||
|     OnFramebufferSizeChanged(); |     OnFramebufferSizeChanged(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |  | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
| enum class LoadCallbackStage; | enum class LoadCallbackStage; | ||||||
| @@ -121,25 +123,21 @@ 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 ForwardKeyPressEvent(QKeyEvent* event); | ||||||
|  |     void ForwardKeyReleaseEvent(QKeyEvent* event); | ||||||
|  |  | ||||||
|     void BackupGeometry(); |     void BackupGeometry(); | ||||||
|     void RestoreGeometry(); |     void RestoreGeometry(); | ||||||
|     void restoreGeometry(const QByteArray& geometry); // overridden |     void restoreGeometry(const QByteArray& geometry); // overridden | ||||||
|     QByteArray saveGeometry();                        // overridden |     QByteArray saveGeometry();                        // overridden | ||||||
|  |  | ||||||
|     qreal windowPixelRatio() const; |     qreal GetWindowPixelRatio() const; | ||||||
|  |     std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const; | ||||||
|  |  | ||||||
|     void closeEvent(QCloseEvent* event) override; |     void closeEvent(QCloseEvent* event) override; | ||||||
|  |  | ||||||
|     void keyPressEvent(QKeyEvent* event) override; |  | ||||||
|     void keyReleaseEvent(QKeyEvent* event) override; |  | ||||||
|  |  | ||||||
|     void mousePressEvent(QMouseEvent* event) override; |  | ||||||
|     void mouseMoveEvent(QMouseEvent* event) override; |  | ||||||
|     void mouseReleaseEvent(QMouseEvent* event) override; |  | ||||||
|  |  | ||||||
|     bool event(QEvent* event) override; |     bool event(QEvent* event) override; | ||||||
|  |  | ||||||
|     void focusOutEvent(QFocusEvent* event) override; |     void focusOutEvent(QFocusEvent* event) override; | ||||||
|  |  | ||||||
|     void OnClientAreaResized(unsigned width, unsigned height); |     void OnClientAreaResized(unsigned width, unsigned height); | ||||||
| @@ -161,7 +159,6 @@ signals: | |||||||
|     void FirstFrameDisplayed(); |     void FirstFrameDisplayed(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const; |  | ||||||
|     void TouchBeginEvent(const QTouchEvent* event); |     void TouchBeginEvent(const QTouchEvent* event); | ||||||
|     void TouchUpdateEvent(const QTouchEvent* event); |     void TouchUpdateEvent(const QTouchEvent* event); | ||||||
|     void TouchEndEvent(); |     void TouchEndEvent(); | ||||||
| @@ -169,11 +166,17 @@ private: | |||||||
|     void OnMinimalClientAreaChangeRequest( |     void OnMinimalClientAreaChangeRequest( | ||||||
|         const std::pair<unsigned, unsigned>& minimal_size) override; |         const std::pair<unsigned, unsigned>& minimal_size) override; | ||||||
|  |  | ||||||
|     GGLWidgetInternal* child; |     QWidget* container = nullptr; | ||||||
|  |     GGLWidgetInternal* child = nullptr; | ||||||
|  |  | ||||||
|     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; | ||||||
|   | |||||||
| @@ -2031,6 +2031,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { | |||||||
|     event->acceptProposedAction(); |     event->acceptProposedAction(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void GMainWindow::keyPressEvent(QKeyEvent* event) { | ||||||
|  |     if (render_window) { | ||||||
|  |         render_window->ForwardKeyPressEvent(event); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GMainWindow::keyReleaseEvent(QKeyEvent* event) { | ||||||
|  |     if (render_window) { | ||||||
|  |         render_window->ForwardKeyReleaseEvent(event); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| bool GMainWindow::ConfirmChangeGame() { | bool GMainWindow::ConfirmChangeGame() { | ||||||
|     if (emu_thread == nullptr) |     if (emu_thread == nullptr) | ||||||
|         return true; |         return true; | ||||||
| @@ -2098,7 +2110,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 | ||||||
|   | |||||||
| @@ -252,4 +252,8 @@ protected: | |||||||
|     void dropEvent(QDropEvent* event) override; |     void dropEvent(QDropEvent* event) override; | ||||||
|     void dragEnterEvent(QDragEnterEvent* event) override; |     void dragEnterEvent(QDragEnterEvent* event) override; | ||||||
|     void dragMoveEvent(QDragMoveEvent* event) override; |     void dragMoveEvent(QDragMoveEvent* event) override; | ||||||
|  |  | ||||||
|  |     // Overrides used to forward signals to the render window when the focus moves out. | ||||||
|  |     void keyPressEvent(QKeyEvent* event) override; | ||||||
|  |     void keyReleaseEvent(QKeyEvent* event) override; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -19,6 +19,37 @@ | |||||||
| #include "input_common/sdl/sdl.h" | #include "input_common/sdl/sdl.h" | ||||||
| #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | ||||||
|  |  | ||||||
|  | class SDLGLContext : public Core::Frontend::GraphicsContext { | ||||||
|  | public: | ||||||
|  |     explicit SDLGLContext() { | ||||||
|  |         // create a hidden window to make the shared context against | ||||||
|  |         window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position | ||||||
|  |                                   SDL_WINDOWPOS_UNDEFINED,     // y position | ||||||
|  |                                   Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, | ||||||
|  |                                   SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); | ||||||
|  |         context = SDL_GL_CreateContext(window); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ~SDLGLContext() { | ||||||
|  |         SDL_GL_DeleteContext(context); | ||||||
|  |         SDL_DestroyWindow(window); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void MakeCurrent() override { | ||||||
|  |         SDL_GL_MakeCurrent(window, context); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void DoneCurrent() override { | ||||||
|  |         SDL_GL_MakeCurrent(window, nullptr); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void SwapBuffers() override {} | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     SDL_Window* window; | ||||||
|  |     SDL_GLContext context; | ||||||
|  | }; | ||||||
|  |  | ||||||
| void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { | void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { | ||||||
|     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); |     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); | ||||||
|     InputCommon::GetMotionEmu()->Tilt(x, y); |     InputCommon::GetMotionEmu()->Tilt(x, y); | ||||||
| @@ -153,6 +184,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||||||
|     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); |     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); | ||||||
|     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); |     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); | ||||||
|     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); |     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); | ||||||
|  |     SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); | ||||||
|  |  | ||||||
|     std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, |     std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, | ||||||
|                                            Common::g_scm_branch, Common::g_scm_desc); |                                            Common::g_scm_branch, Common::g_scm_desc); | ||||||
| @@ -171,7 +203,6 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||||||
|     if (fullscreen) { |     if (fullscreen) { | ||||||
|         Fullscreen(); |         Fullscreen(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     gl_context = SDL_GL_CreateContext(render_window); |     gl_context = SDL_GL_CreateContext(render_window); | ||||||
|  |  | ||||||
|     if (gl_context == nullptr) { |     if (gl_context == nullptr) { | ||||||
| @@ -278,3 +309,7 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest( | |||||||
|  |  | ||||||
|     SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); |     SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const { | ||||||
|  |     return std::make_unique<SDLGLContext>(); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -27,6 +27,8 @@ public: | |||||||
|     /// Releases the GL context from the caller thread |     /// Releases the GL context from the caller thread | ||||||
|     void DoneCurrent() override; |     void DoneCurrent() override; | ||||||
|  |  | ||||||
|  |     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | ||||||
|  |  | ||||||
|     /// Whether the window is still open, and a close request hasn't yet been sent |     /// Whether the window is still open, and a close request hasn't yet been sent | ||||||
|     bool IsOpen() const; |     bool IsOpen() const; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user