From 91255b8802ac81ddd6012738e92a105865ba8464 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Mon, 16 Sep 2019 20:38:48 -0600 Subject: [PATCH] Change Qt to use QOpenGLWidget and support shared context and texture mailbox --- src/citra_qt/bootmanager.cpp | 142 +++++++++++------------------------ src/citra_qt/bootmanager.h | 38 +++++++--- src/citra_qt/main.cpp | 23 ++++-- 3 files changed, 85 insertions(+), 118 deletions(-) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 400a0104c3..3ccfbb19fc 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -1,31 +1,33 @@ #include #include #include +#include +#include +#include #include #include #include - #include "citra_qt/bootmanager.h" #include "common/microprofile.h" #include "common/scm_rev.h" #include "core/3ds.h" #include "core/core.h" +#include "core/frontend/scope_acquire_context.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" #include "network/network.h" +#include "video_core/renderer_base.h" #include "video_core/video_core.h" -EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} +EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {} EmuThread::~EmuThread() = default; void EmuThread::run() { - render_window->MakeCurrent(); - MicroProfileOnThreadCreate("EmuThread"); - + Frontend::ScopeAcquireContext scope(core_context); // Holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step. @@ -72,43 +74,10 @@ void EmuThread::run() { #if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); #endif - - render_window->moveContext(); } -// 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 { -public: - GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) - : QGLWidget(fmt, parent), parent(parent) {} - - void paintEvent(QPaintEvent* ev) override { - if (do_painting) { - QPainter painter(this); - } - } - - void resizeEvent(QResizeEvent* ev) override { - parent->OnClientAreaResized(ev->size().width(), ev->size().height()); - parent->OnFramebufferSizeChanged(); - } - - void DisablePainting() { - do_painting = false; - } - void EnablePainting() { - do_painting = true; - } - -private: - GRenderWindow* parent; - bool do_painting; -}; - GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), child(nullptr), emu_thread(emu_thread) { + : QOpenGLWidget(parent), emu_thread(emu_thread) { setWindowTitle(QStringLiteral("Citra %1 | %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); @@ -121,35 +90,12 @@ GRenderWindow::~GRenderWindow() { InputCommon::Shutdown(); } -void GRenderWindow::moveContext() { - DoneCurrent(); - - // If the thread started running, move the GL Context to the new thread. Otherwise, move it - // back. - auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) - ? emu_thread - : qApp->thread(); - child->context()->moveToThread(thread); -} - -void GRenderWindow::SwapBuffers() { - // In our multi-threaded QGLWidget 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(); - - child->swapBuffers(); -} - void GRenderWindow::MakeCurrent() { - child->makeCurrent(); + core_context->MakeCurrent(); } void GRenderWindow::DoneCurrent() { - child->doneCurrent(); + core_context->DoneCurrent(); } void GRenderWindow::PollEvents() {} @@ -163,8 +109,8 @@ void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the // framebuffer size const qreal pixel_ratio = windowPixelRatio(); - const u32 width = child->QPaintDevice::width() * pixel_ratio; - const u32 height = child->QPaintDevice::height() * pixel_ratio; + const u32 width = this->width() * pixel_ratio; + const u32 height = this->height() * pixel_ratio; UpdateCurrentFramebufferLayout(width, height); } @@ -298,42 +244,16 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { InputCommon::GetKeyboard()->ReleaseAllKeys(); } -void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { - NotifyClientAreaSizeChanged(std::make_pair(width, height)); +void GRenderWindow::resizeEvent(QResizeEvent* event) { + QOpenGLWidget::resizeEvent(event); + NotifyClientAreaSizeChanged(std::make_pair(event->size().width(), event->size().height())); + OnFramebufferSizeChanged(); } void GRenderWindow::InitRenderTarget() { - if (child) { - delete child; - } - - if (layout()) { - delete layout(); - } - // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose - QGLFormat fmt; - fmt.setVersion(3, 3); - fmt.setProfile(QGLFormat::CoreProfile); - fmt.setSwapInterval(Settings::values.vsync_enabled); - - // 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); - QBoxLayout* layout = new QHBoxLayout(this); - - resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); - layout->addWidget(child); - layout->setMargin(0); - setLayout(layout); - - OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); - - OnFramebufferSizeChanged(); - NotifyClientAreaSizeChanged(std::pair(child->width(), child->height())); - + core_context = CreateSharedContext(); BackupGeometry(); } @@ -361,12 +281,15 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair minimal void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { this->emu_thread = emu_thread; - child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { emu_thread = nullptr; - child->EnablePainting(); +} + +void GRenderWindow::paintGL() { + VideoCore::g_renderer->Present(); + update(); } void GRenderWindow::showEvent(QShowEvent* event) { @@ -376,3 +299,24 @@ void GRenderWindow::showEvent(QShowEvent* event) { connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, Qt::UniqueConnection); } + +std::unique_ptr GRenderWindow::CreateSharedContext() const { + return std::make_unique(QOpenGLContext::globalShareContext()); +} + +GGLContext::GGLContext(QOpenGLContext* shared_context) + : context(new QOpenGLContext(shared_context->parent())), + surface(new QOffscreenSurface(nullptr, shared_context->parent())) { + context->setShareContext(shared_context); + context->create(); + surface->setFormat(shared_context->format()); + surface->create(); +} + +void GGLContext::MakeCurrent() { + context->makeCurrent(surface); +} + +void GGLContext::DoneCurrent() { + context->doneCurrent(); +} diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index b36d45868c..4edb96d72a 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #include #include "common/thread.h" #include "core/core.h" @@ -17,16 +16,30 @@ class QKeyEvent; class QScreen; class QTouchEvent; +class QOffscreenSurface; +class QOpenGLContext; -class GGLWidgetInternal; class GMainWindow; class GRenderWindow; +class GGLContext : public Frontend::GraphicsContext { +public: + explicit GGLContext(QOpenGLContext* shared_context); + + void MakeCurrent() override; + + void DoneCurrent() override; + +private: + QOpenGLContext* context; + QOffscreenSurface* surface; +}; + class EmuThread final : public QThread { Q_OBJECT public: - explicit EmuThread(GRenderWindow* render_window); + explicit EmuThread(Frontend::GraphicsContext& context); ~EmuThread() override; /** @@ -80,7 +93,7 @@ private: std::mutex running_mutex; std::condition_variable running_cv; - GRenderWindow* render_window; + Frontend::GraphicsContext& core_context; signals: /** @@ -104,18 +117,20 @@ signals: void ErrorThrown(Core::System::ResultStatus, std::string); }; -class GRenderWindow : public QWidget, public Frontend::EmuWindow { +class GRenderWindow : public QOpenGLWidget, public Frontend::EmuWindow { Q_OBJECT public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); ~GRenderWindow() override; - // EmuWindow implementation - void SwapBuffers() override; + // EmuWindow implementation. void MakeCurrent() override; void DoneCurrent() override; void PollEvents() override; + std::unique_ptr CreateSharedContext() const override; + + void paintGL() override; void BackupGeometry(); void RestoreGeometry(); @@ -126,6 +141,8 @@ public: void closeEvent(QCloseEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -137,14 +154,11 @@ public: void focusOutEvent(QFocusEvent* event) override; - void OnClientAreaResized(u32 width, u32 height); - void InitRenderTarget(); void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); public slots: - void moveContext(); // overridden void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); @@ -162,7 +176,7 @@ private: void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; - GGLWidgetInternal* child; + std::unique_ptr core_context; QByteArray geometry; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 11da2be202..9d752d3cee 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -5,12 +5,11 @@ #include #include #include -#include -#define QT_NO_OPENGL #include #include #include #include +#include #include #include #include @@ -72,6 +71,7 @@ #include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/archive_source_sd_savedata.h" #include "core/frontend/applets/default_applets.h" +#include "core/frontend/scope_acquire_context.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/nfc/nfc.h" @@ -768,13 +768,14 @@ bool GMainWindow::LoadROM(const QString& filename) { ShutdownGame(); render_window->InitRenderTarget(); - render_window->MakeCurrent(); + + Frontend::ScopeAcquireContext scope(*render_window); const QString below_gl33_title = tr("OpenGL 3.3 Unsupported"); const QString below_gl33_message = tr("Your GPU may not support OpenGL 3.3, or you do not " "have the latest graphics driver."); - if (!gladLoadGL()) { + if (!QOpenGLContext::globalShareContext()->versionFunctions()) { QMessageBox::critical(this, below_gl33_title, below_gl33_message); return false; } @@ -893,9 +894,8 @@ void GMainWindow::BootGame(const QString& filename) { return; // Create and start the emulation thread - emu_thread = std::make_unique(render_window); + emu_thread = std::make_unique(*render_window); emit EmulationStarting(emu_thread.get()); - render_window->moveContext(); emu_thread->start(); connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -2050,11 +2050,20 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName("Citra team"); QCoreApplication::setApplicationName("Citra"); + QSurfaceFormat format; + format.setVersion(3, 3); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setSwapInterval(1); + // TODO: expose a setting for buffer value (ie default/single/double/triple) + format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + QSurfaceFormat::setDefaultFormat(format); + #ifdef __APPLE__ std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); #endif - + QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); // Qt changes the locale and causes issues in float conversion using std::to_string() when