mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Change Qt to use QOpenGLWidget and support shared context and texture mailbox
This commit is contained in:
		
							parent
							
								
									27d0fc64d0
								
							
						
					
					
						commit
						91255b8802
					
				
					 3 changed files with 85 additions and 118 deletions
				
			
		|  | @ -1,31 +1,33 @@ | |||
| #include <QApplication> | ||||
| #include <QHBoxLayout> | ||||
| #include <QKeyEvent> | ||||
| #include <QOffScreenSurface> | ||||
| #include <QOpenGLContext> | ||||
| #include <QOpenGLFunctions> | ||||
| #include <QScreen> | ||||
| #include <QWindow> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| #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<unsigned, unsigned>(child->width(), child->height())); | ||||
| 
 | ||||
|     core_context = CreateSharedContext(); | ||||
|     BackupGeometry(); | ||||
| } | ||||
| 
 | ||||
|  | @ -361,12 +281,15 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> 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<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||||
|     return std::make_unique<GGLContext>(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(); | ||||
| } | ||||
|  |  | |||
|  | @ -7,8 +7,7 @@ | |||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <QGLWidget> | ||||
| #include <QImage> | ||||
| #include <QOpenGLWidget> | ||||
| #include <QThread> | ||||
| #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<Frontend::GraphicsContext> 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<u32, u32> minimal_size) override; | ||||
| 
 | ||||
|     GGLWidgetInternal* child; | ||||
|     std::unique_ptr<GraphicsContext> core_context; | ||||
| 
 | ||||
|     QByteArray geometry; | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,12 +5,11 @@ | |||
| #include <clocale> | ||||
| #include <memory> | ||||
| #include <thread> | ||||
| #include <glad/glad.h> | ||||
| #define QT_NO_OPENGL | ||||
| #include <QDesktopWidget> | ||||
| #include <QFileDialog> | ||||
| #include <QFutureWatcher> | ||||
| #include <QMessageBox> | ||||
| #include <QOpenGLFunctions_3_3_Core> | ||||
| #include <QSysInfo> | ||||
| #include <QtConcurrent/QtConcurrentRun> | ||||
| #include <QtGui> | ||||
|  | @ -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<QOpenGLFunctions_3_3_Core>()) { | ||||
|         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<EmuThread>(render_window); | ||||
|     emu_thread = std::make_unique<EmuThread>(*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
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue