diff options
| -rw-r--r-- | src/core/frontend/emu_window.h | 39 | ||||
| -rw-r--r-- | src/yuzu/bootmanager.cpp | 91 | ||||
| -rw-r--r-- | src/yuzu/bootmanager.h | 10 | ||||
| -rw-r--r-- | 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 | |||
| @@ -13,6 +13,23 @@ | |||
| 13 | namespace Core::Frontend { | 13 | namespace Core::Frontend { |
| 14 | 14 | ||
| 15 | /** | 15 | /** |
| 16 | * Represents a graphics context that can be used for background computation or drawing. If the | ||
| 17 | * graphics backend doesn't require the context, then the implementation of these methods can be | ||
| 18 | * stubs | ||
| 19 | */ | ||
| 20 | class GraphicsContext { | ||
| 21 | public: | ||
| 22 | /// Makes the graphics context current for the caller thread | ||
| 23 | virtual void MakeCurrent() = 0; | ||
| 24 | |||
| 25 | /// Releases (dunno if this is the "right" word) the context from the caller thread | ||
| 26 | virtual void DoneCurrent() = 0; | ||
| 27 | |||
| 28 | /// Swap buffers to display the next frame | ||
| 29 | virtual void SwapBuffers() = 0; | ||
| 30 | }; | ||
| 31 | |||
| 32 | /** | ||
| 16 | * Abstraction class used to provide an interface between emulation code and the frontend | 33 | * Abstraction class used to provide an interface between emulation code and the frontend |
| 17 | * (e.g. SDL, QGLWidget, GLFW, etc...). | 34 | * (e.g. SDL, QGLWidget, GLFW, etc...). |
| 18 | * | 35 | * |
| @@ -30,7 +47,7 @@ namespace Core::Frontend { | |||
| 30 | * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please | 47 | * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please |
| 31 | * re-read the upper points again and think about it if you don't see this. | 48 | * re-read the upper points again and think about it if you don't see this. |
| 32 | */ | 49 | */ |
| 33 | class EmuWindow { | 50 | class EmuWindow : public GraphicsContext { |
| 34 | public: | 51 | public: |
| 35 | /// Data structure to store emuwindow configuration | 52 | /// Data structure to store emuwindow configuration |
| 36 | struct WindowConfig { | 53 | struct WindowConfig { |
| @@ -40,17 +57,21 @@ public: | |||
| 40 | std::pair<unsigned, unsigned> min_client_area_size; | 57 | std::pair<unsigned, unsigned> min_client_area_size; |
| 41 | }; | 58 | }; |
| 42 | 59 | ||
| 43 | /// Swap buffers to display the next frame | ||
| 44 | virtual void SwapBuffers() = 0; | ||
| 45 | |||
| 46 | /// Polls window events | 60 | /// Polls window events |
| 47 | virtual void PollEvents() = 0; | 61 | virtual void PollEvents() = 0; |
| 48 | 62 | ||
| 49 | /// Makes the graphics context current for the caller thread | 63 | /** |
| 50 | virtual void MakeCurrent() = 0; | 64 | * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This |
| 51 | 65 | * context can be used from other threads for background graphics computation. If the frontend | |
| 52 | /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread | 66 | * is using a graphics backend that doesn't need anything specific to run on a different thread, |
| 53 | virtual void DoneCurrent() = 0; | 67 | * then it can use a stubbed implemenation for GraphicsContext. |
| 68 | * | ||
| 69 | * If the return value is null, then the core should assume that the frontend cannot provide a | ||
| 70 | * Shared Context | ||
| 71 | */ | ||
| 72 | virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const { | ||
| 73 | return nullptr; | ||
| 74 | } | ||
| 54 | 75 | ||
| 55 | /** | 76 | /** |
| 56 | * Signal that a touch pressed event has occurred (e.g. mouse click pressed) | 77 | * 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 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 1 | #include <QApplication> | 5 | #include <QApplication> |
| 2 | #include <QHBoxLayout> | 6 | #include <QHBoxLayout> |
| 3 | #include <QKeyEvent> | 7 | #include <QKeyEvent> |
| 8 | #include <QOffscreenSurface> | ||
| 9 | #include <QOpenGLWindow> | ||
| 10 | #include <QPainter> | ||
| 4 | #include <QScreen> | 11 | #include <QScreen> |
| 5 | #include <QWindow> | 12 | #include <QWindow> |
| 6 | #include <fmt/format.h> | 13 | #include <fmt/format.h> |
| @@ -73,13 +80,36 @@ void EmuThread::run() { | |||
| 73 | render_window->moveContext(); | 80 | render_window->moveContext(); |
| 74 | } | 81 | } |
| 75 | 82 | ||
| 83 | class GGLContext : public Core::Frontend::GraphicsContext { | ||
| 84 | public: | ||
| 85 | explicit GGLContext(QOpenGLContext* shared_context) : surface() { | ||
| 86 | context = std::make_unique<QOpenGLContext>(shared_context); | ||
| 87 | surface.setFormat(shared_context->format()); | ||
| 88 | surface.create(); | ||
| 89 | } | ||
| 90 | |||
| 91 | void MakeCurrent() override { | ||
| 92 | context->makeCurrent(&surface); | ||
| 93 | } | ||
| 94 | |||
| 95 | void DoneCurrent() override { | ||
| 96 | context->doneCurrent(); | ||
| 97 | } | ||
| 98 | |||
| 99 | void SwapBuffers() override {} | ||
| 100 | |||
| 101 | private: | ||
| 102 | std::unique_ptr<QOpenGLContext> context; | ||
| 103 | QOffscreenSurface surface; | ||
| 104 | }; | ||
| 105 | |||
| 76 | // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL | 106 | // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL |
| 77 | // context. | 107 | // context. |
| 78 | // The corresponding functionality is handled in EmuThread instead | 108 | // The corresponding functionality is handled in EmuThread instead |
| 79 | class GGLWidgetInternal : public QGLWidget { | 109 | class GGLWidgetInternal : public QOpenGLWindow { |
| 80 | public: | 110 | public: |
| 81 | GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) | 111 | GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) |
| 82 | : QGLWidget(fmt, parent), parent(parent) {} | 112 | : QOpenGLWindow(shared_context), parent(parent) {} |
| 83 | 113 | ||
| 84 | void paintEvent(QPaintEvent* ev) override { | 114 | void paintEvent(QPaintEvent* ev) override { |
| 85 | if (do_painting) { | 115 | if (do_painting) { |
| @@ -105,7 +135,7 @@ private: | |||
| 105 | }; | 135 | }; |
| 106 | 136 | ||
| 107 | GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | 137 | GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) |
| 108 | : QWidget(parent), child(nullptr), emu_thread(emu_thread) { | 138 | : QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) { |
| 109 | 139 | ||
| 110 | setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") | 140 | setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") |
| 111 | .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); | 141 | .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); |
| @@ -129,19 +159,19 @@ void GRenderWindow::moveContext() { | |||
| 129 | auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) | 159 | auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) |
| 130 | ? emu_thread | 160 | ? emu_thread |
| 131 | : qApp->thread(); | 161 | : qApp->thread(); |
| 132 | child->context()->moveToThread(thread); | 162 | context->moveToThread(thread); |
| 133 | } | 163 | } |
| 134 | 164 | ||
| 135 | void GRenderWindow::SwapBuffers() { | 165 | void GRenderWindow::SwapBuffers() { |
| 136 | // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`, | 166 | // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`, |
| 137 | // since we never call `doneCurrent` in this thread. | 167 | // since we never call `doneCurrent` in this thread. |
| 138 | // However: | 168 | // However: |
| 139 | // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called | 169 | // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called |
| 140 | // since the last time `swapBuffers` was executed; | 170 | // since the last time `swapBuffers` was executed; |
| 141 | // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. | 171 | // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. |
| 142 | child->makeCurrent(); | 172 | context->makeCurrent(child); |
| 143 | 173 | ||
| 144 | child->swapBuffers(); | 174 | context->swapBuffers(child); |
| 145 | if (!first_frame) { | 175 | if (!first_frame) { |
| 146 | emit FirstFrameDisplayed(); | 176 | emit FirstFrameDisplayed(); |
| 147 | first_frame = true; | 177 | first_frame = true; |
| @@ -149,11 +179,11 @@ void GRenderWindow::SwapBuffers() { | |||
| 149 | } | 179 | } |
| 150 | 180 | ||
| 151 | void GRenderWindow::MakeCurrent() { | 181 | void GRenderWindow::MakeCurrent() { |
| 152 | child->makeCurrent(); | 182 | context->makeCurrent(child); |
| 153 | } | 183 | } |
| 154 | 184 | ||
| 155 | void GRenderWindow::DoneCurrent() { | 185 | void GRenderWindow::DoneCurrent() { |
| 156 | child->doneCurrent(); | 186 | context->doneCurrent(); |
| 157 | } | 187 | } |
| 158 | 188 | ||
| 159 | void GRenderWindow::PollEvents() {} | 189 | void GRenderWindow::PollEvents() {} |
| @@ -173,7 +203,7 @@ void GRenderWindow::OnFramebufferSizeChanged() { | |||
| 173 | } | 203 | } |
| 174 | 204 | ||
| 175 | void GRenderWindow::BackupGeometry() { | 205 | void GRenderWindow::BackupGeometry() { |
| 176 | geometry = ((QGLWidget*)this)->saveGeometry(); | 206 | geometry = ((QWidget*)this)->saveGeometry(); |
| 177 | } | 207 | } |
| 178 | 208 | ||
| 179 | void GRenderWindow::RestoreGeometry() { | 209 | void GRenderWindow::RestoreGeometry() { |
| @@ -191,7 +221,7 @@ QByteArray GRenderWindow::saveGeometry() { | |||
| 191 | // If we are a top-level widget, store the current geometry | 221 | // If we are a top-level widget, store the current geometry |
| 192 | // otherwise, store the last backup | 222 | // otherwise, store the last backup |
| 193 | if (parent() == nullptr) | 223 | if (parent() == nullptr) |
| 194 | return ((QGLWidget*)this)->saveGeometry(); | 224 | return ((QWidget*)this)->saveGeometry(); |
| 195 | else | 225 | else |
| 196 | return geometry; | 226 | return geometry; |
| 197 | } | 227 | } |
| @@ -305,7 +335,19 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { | |||
| 305 | NotifyClientAreaSizeChanged(std::make_pair(width, height)); | 335 | NotifyClientAreaSizeChanged(std::make_pair(width, height)); |
| 306 | } | 336 | } |
| 307 | 337 | ||
| 338 | std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||
| 339 | return std::make_unique<GGLContext>(shared_context.get()); | ||
| 340 | } | ||
| 341 | |||
| 308 | void GRenderWindow::InitRenderTarget() { | 342 | void GRenderWindow::InitRenderTarget() { |
| 343 | if (shared_context) { | ||
| 344 | shared_context.reset(); | ||
| 345 | } | ||
| 346 | |||
| 347 | if (context) { | ||
| 348 | context.reset(); | ||
| 349 | } | ||
| 350 | |||
| 309 | if (child) { | 351 | if (child) { |
| 310 | delete child; | 352 | delete child; |
| 311 | } | 353 | } |
| @@ -318,19 +360,28 @@ void GRenderWindow::InitRenderTarget() { | |||
| 318 | 360 | ||
| 319 | // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, | 361 | // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, |
| 320 | // WA_DontShowOnScreen, WA_DeleteOnClose | 362 | // WA_DontShowOnScreen, WA_DeleteOnClose |
| 321 | QGLFormat fmt; | 363 | QSurfaceFormat fmt; |
| 322 | fmt.setVersion(4, 3); | 364 | fmt.setVersion(4, 3); |
| 323 | fmt.setProfile(QGLFormat::CoreProfile); | 365 | fmt.setProfile(QSurfaceFormat::CoreProfile); |
| 366 | // TODO: expose a setting for buffer value (ie default/single/double/triple) | ||
| 367 | fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); | ||
| 368 | shared_context = std::make_unique<QOpenGLContext>(); | ||
| 369 | shared_context->setFormat(fmt); | ||
| 370 | shared_context->create(); | ||
| 371 | context = std::make_unique<QOpenGLContext>(); | ||
| 372 | context->setShareContext(shared_context.get()); | ||
| 373 | context->setFormat(fmt); | ||
| 374 | context->create(); | ||
| 324 | fmt.setSwapInterval(false); | 375 | fmt.setSwapInterval(false); |
| 325 | 376 | ||
| 326 | // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X | 377 | child = new GGLWidgetInternal(this, shared_context.get()); |
| 327 | fmt.setOption(QGL::NoDeprecatedFunctions); | 378 | QWidget* container = QWidget::createWindowContainer(child, this); |
| 328 | |||
| 329 | child = new GGLWidgetInternal(fmt, this); | ||
| 330 | QBoxLayout* layout = new QHBoxLayout(this); | 379 | QBoxLayout* layout = new QHBoxLayout(this); |
| 331 | 380 | ||
| 332 | resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | 381 | resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); |
| 333 | layout->addWidget(child); | 382 | child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); |
| 383 | container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||
| 384 | layout->addWidget(container); | ||
| 334 | layout->setMargin(0); | 385 | layout->setMargin(0); |
| 335 | setLayout(layout); | 386 | setLayout(layout); |
| 336 | 387 | ||
| @@ -340,6 +391,8 @@ void GRenderWindow::InitRenderTarget() { | |||
| 340 | NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); | 391 | NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); |
| 341 | 392 | ||
| 342 | BackupGeometry(); | 393 | BackupGeometry(); |
| 394 | // show causes the window to actually be created and the gl context as well | ||
| 395 | show(); | ||
| 343 | } | 396 | } |
| 344 | 397 | ||
| 345 | void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { | 398 | 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 @@ | |||
| 7 | #include <atomic> | 7 | #include <atomic> |
| 8 | #include <condition_variable> | 8 | #include <condition_variable> |
| 9 | #include <mutex> | 9 | #include <mutex> |
| 10 | #include <QGLWidget> | ||
| 11 | #include <QImage> | 10 | #include <QImage> |
| 12 | #include <QThread> | 11 | #include <QThread> |
| 12 | #include <QWidget> | ||
| 13 | #include "common/thread.h" | 13 | #include "common/thread.h" |
| 14 | #include "core/core.h" | 14 | #include "core/core.h" |
| 15 | #include "core/frontend/emu_window.h" | 15 | #include "core/frontend/emu_window.h" |
| @@ -21,6 +21,8 @@ class QTouchEvent; | |||
| 21 | class GGLWidgetInternal; | 21 | class GGLWidgetInternal; |
| 22 | class GMainWindow; | 22 | class GMainWindow; |
| 23 | class GRenderWindow; | 23 | class GRenderWindow; |
| 24 | class QSurface; | ||
| 25 | class QOpenGLContext; | ||
| 24 | 26 | ||
| 25 | class EmuThread : public QThread { | 27 | class EmuThread : public QThread { |
| 26 | Q_OBJECT | 28 | Q_OBJECT |
| @@ -115,6 +117,7 @@ public: | |||
| 115 | void MakeCurrent() override; | 117 | void MakeCurrent() override; |
| 116 | void DoneCurrent() override; | 118 | void DoneCurrent() override; |
| 117 | void PollEvents() override; | 119 | void PollEvents() override; |
| 120 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | ||
| 118 | 121 | ||
| 119 | void BackupGeometry(); | 122 | void BackupGeometry(); |
| 120 | void RestoreGeometry(); | 123 | void RestoreGeometry(); |
| @@ -168,6 +171,11 @@ private: | |||
| 168 | QByteArray geometry; | 171 | QByteArray geometry; |
| 169 | 172 | ||
| 170 | EmuThread* emu_thread; | 173 | EmuThread* emu_thread; |
| 174 | // Context that backs the GGLWidgetInternal (and will be used by core to render) | ||
| 175 | std::unique_ptr<QOpenGLContext> context; | ||
| 176 | // Context that will be shared between all newly created contexts. This should never be made | ||
| 177 | // current | ||
| 178 | std::unique_ptr<QOpenGLContext> shared_context; | ||
| 171 | 179 | ||
| 172 | /// Temporary storage of the screenshot taken | 180 | /// Temporary storage of the screenshot taken |
| 173 | QImage screenshot_image; | 181 | 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[]) { | |||
| 2032 | QCoreApplication::setOrganizationName("yuzu team"); | 2032 | QCoreApplication::setOrganizationName("yuzu team"); |
| 2033 | QCoreApplication::setApplicationName("yuzu"); | 2033 | QCoreApplication::setApplicationName("yuzu"); |
| 2034 | 2034 | ||
| 2035 | QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | 2035 | // Enables the core to make the qt created contexts current on std::threads |
| 2036 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | ||
| 2036 | QApplication app(argc, argv); | 2037 | QApplication app(argc, argv); |
| 2037 | 2038 | ||
| 2038 | // Qt changes the locale and causes issues in float conversion using std::to_string() when | 2039 | // Qt changes the locale and causes issues in float conversion using std::to_string() when |