mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #4940 from jroweboy/presentation-thread
Split Presentation thread from Render thread
This commit is contained in:
		
						commit
						439d550850
					
				
					 27 changed files with 899 additions and 243 deletions
				
			
		|  | @ -1,31 +1,50 @@ | |||
| // Copyright 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QApplication> | ||||
| #include <QDragEnterEvent> | ||||
| #include <QHBoxLayout> | ||||
| #include <QKeyEvent> | ||||
| #include <QOffscreenSurface> | ||||
| #include <QOpenGLContext> | ||||
| #include <QOpenGLFunctions> | ||||
| #include <QOpenGLFunctions_3_3_Core> | ||||
| #include <QOpenGLWindow> | ||||
| #include <QScreen> | ||||
| #include <QWindow> | ||||
| #include <fmt/format.h> | ||||
| 
 | ||||
| #include "citra_qt/bootmanager.h" | ||||
| #include "citra_qt/main.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; | ||||
| 
 | ||||
| static GMainWindow* GetMainWindow() { | ||||
|     for (QWidget* w : qApp->topLevelWidgets()) { | ||||
|         if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { | ||||
|             return main; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| 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,48 +91,104 @@ 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) {} | ||||
| OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) | ||||
|     : QWindow(parent), event_handler(event_handler), | ||||
|       context(new QOpenGLContext(shared_context->parent())) { | ||||
| 
 | ||||
|     void paintEvent(QPaintEvent* ev) override { | ||||
|         if (do_painting) { | ||||
|             QPainter painter(this); | ||||
|         } | ||||
|     } | ||||
|     // disable vsync for any shared contexts
 | ||||
|     auto format = shared_context->format(); | ||||
|     format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0); | ||||
|     this->setFormat(format); | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* ev) override { | ||||
|         parent->OnClientAreaResized(ev->size().width(), ev->size().height()); | ||||
|         parent->OnFramebufferSizeChanged(); | ||||
|     } | ||||
|     context->setShareContext(shared_context); | ||||
|     context->setScreen(this->screen()); | ||||
|     context->setFormat(format); | ||||
|     context->create(); | ||||
| 
 | ||||
|     void DisablePainting() { | ||||
|         do_painting = false; | ||||
|     } | ||||
|     void EnablePainting() { | ||||
|         do_painting = true; | ||||
|     } | ||||
|     LOG_WARNING(Frontend, "OpenGLWindow context format Interval {}", | ||||
|                 context->format().swapInterval()); | ||||
| 
 | ||||
| private: | ||||
|     GRenderWindow* parent; | ||||
|     bool do_painting; | ||||
| }; | ||||
|     LOG_WARNING(Frontend, "OpenGLWindow surface format interval {}", this->format().swapInterval()); | ||||
| 
 | ||||
|     setSurfaceType(QWindow::OpenGLSurface); | ||||
| 
 | ||||
|     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
 | ||||
|     // WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||
| } | ||||
| 
 | ||||
| OpenGLWindow::~OpenGLWindow() { | ||||
|     context->doneCurrent(); | ||||
| } | ||||
| 
 | ||||
| void OpenGLWindow::Present() { | ||||
|     if (!isExposed()) | ||||
|         return; | ||||
|     context->makeCurrent(this); | ||||
|     VideoCore::g_renderer->TryPresent(100); | ||||
|     context->swapBuffers(this); | ||||
|     auto f = context->versionFunctions<QOpenGLFunctions_3_3_Core>(); | ||||
|     f->glFinish(); | ||||
|     QWindow::requestUpdate(); | ||||
| } | ||||
| 
 | ||||
| bool OpenGLWindow::event(QEvent* event) { | ||||
|     switch (event->type()) { | ||||
|     case QEvent::UpdateRequest: | ||||
|         Present(); | ||||
|         return true; | ||||
|     case QEvent::MouseButtonPress: | ||||
|     case QEvent::MouseButtonRelease: | ||||
|     case QEvent::MouseButtonDblClick: | ||||
|     case QEvent::MouseMove: | ||||
|     case QEvent::KeyPress: | ||||
|     case QEvent::KeyRelease: | ||||
|     case QEvent::FocusIn: | ||||
|     case QEvent::FocusOut: | ||||
|     case QEvent::FocusAboutToChange: | ||||
|     case QEvent::Enter: | ||||
|     case QEvent::Leave: | ||||
|     case QEvent::Wheel: | ||||
|     case QEvent::TabletMove: | ||||
|     case QEvent::TabletPress: | ||||
|     case QEvent::TabletRelease: | ||||
|     case QEvent::TabletEnterProximity: | ||||
|     case QEvent::TabletLeaveProximity: | ||||
|     case QEvent::TouchBegin: | ||||
|     case QEvent::TouchUpdate: | ||||
|     case QEvent::TouchEnd: | ||||
|     case QEvent::InputMethodQuery: | ||||
|     case QEvent::TouchCancel: | ||||
|         return QCoreApplication::sendEvent(event_handler, event); | ||||
|     case QEvent::Drop: | ||||
|         GetMainWindow()->DropAction(static_cast<QDropEvent*>(event)); | ||||
|         return true; | ||||
|     case QEvent::DragResponse: | ||||
|     case QEvent::DragEnter: | ||||
|     case QEvent::DragLeave: | ||||
|     case QEvent::DragMove: | ||||
|         GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event)); | ||||
|         return true; | ||||
|     default: | ||||
|         return QWindow::event(event); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OpenGLWindow::exposeEvent(QExposeEvent* event) { | ||||
|     QWindow::requestUpdate(); | ||||
|     QWindow::exposeEvent(event); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | ||||
|     : QWidget(parent), child(nullptr), emu_thread(emu_thread) { | ||||
|     : QWidget(parent), emu_thread(emu_thread) { | ||||
| 
 | ||||
|     setWindowTitle(QStringLiteral("Citra %1 | %2-%3") | ||||
|                        .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); | ||||
|     setAttribute(Qt::WA_AcceptTouchEvents); | ||||
| 
 | ||||
|     auto layout = new QHBoxLayout(this); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
|     InputCommon::Init(); | ||||
| } | ||||
| 
 | ||||
|  | @ -121,35 +196,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 +215,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); | ||||
| } | ||||
| 
 | ||||
|  | @ -194,8 +246,7 @@ QByteArray GRenderWindow::saveGeometry() { | |||
| } | ||||
| 
 | ||||
| qreal GRenderWindow::windowPixelRatio() const { | ||||
|     // windowHandle() might not be accessible until the window is displayed to screen.
 | ||||
|     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||||
|     return devicePixelRatio(); | ||||
| } | ||||
| 
 | ||||
| std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { | ||||
|  | @ -279,15 +330,19 @@ void GRenderWindow::TouchEndEvent() { | |||
| } | ||||
| 
 | ||||
| bool GRenderWindow::event(QEvent* event) { | ||||
|     if (event->type() == QEvent::TouchBegin) { | ||||
|     switch (event->type()) { | ||||
|     case QEvent::TouchBegin: | ||||
|         TouchBeginEvent(static_cast<QTouchEvent*>(event)); | ||||
|         return true; | ||||
|     } else if (event->type() == QEvent::TouchUpdate) { | ||||
|     case QEvent::TouchUpdate: | ||||
|         TouchUpdateEvent(static_cast<QTouchEvent*>(event)); | ||||
|         return true; | ||||
|     } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) { | ||||
|     case QEvent::TouchEnd: | ||||
|     case QEvent::TouchCancel: | ||||
|         TouchEndEvent(); | ||||
|         return true; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return QWidget::event(event); | ||||
|  | @ -298,45 +353,36 @@ 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) { | ||||
|     QWidget::resizeEvent(event); | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::InitRenderTarget() { | ||||
|     if (child) { | ||||
|         delete child; | ||||
|     } | ||||
|     ReleaseRenderTarget(); | ||||
| 
 | ||||
|     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); | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; | ||||
|     child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext()); | ||||
|     child_window->create(); | ||||
|     child_widget = createWindowContainer(child_window, this); | ||||
|     child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); | ||||
|     layout()->addWidget(child_widget); | ||||
| 
 | ||||
|     core_context = CreateSharedContext(); | ||||
|     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())); | ||||
| 
 | ||||
|     BackupGeometry(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ReleaseRenderTarget() { | ||||
|     if (child_widget) { | ||||
|         layout()->removeWidget(child_widget); | ||||
|         delete child_widget; | ||||
|         child_widget = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { | ||||
|     if (res_scale == 0) | ||||
|         res_scale = VideoCore::GetResolutionScaleFactor(); | ||||
|  | @ -361,18 +407,40 @@ 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::showEvent(QShowEvent* event) { | ||||
|     QWidget::showEvent(event); | ||||
| 
 | ||||
|     // windowHandle() is not initialized until the Window is shown, so we connect it here.
 | ||||
|     connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, | ||||
|             Qt::UniqueConnection); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | ||||
|     return std::make_unique<GLContext>(QOpenGLContext::globalShareContext()); | ||||
| } | ||||
| 
 | ||||
| GLContext::GLContext(QOpenGLContext* shared_context) | ||||
|     : context(new QOpenGLContext(shared_context->parent())), | ||||
|       surface(new QOffscreenSurface(nullptr)) { | ||||
| 
 | ||||
|     // disable vsync for any shared contexts
 | ||||
|     auto format = shared_context->format(); | ||||
|     format.setSwapInterval(0); | ||||
| 
 | ||||
|     context->setShareContext(shared_context); | ||||
|     context->setFormat(format); | ||||
|     context->create(); | ||||
|     surface->setParent(shared_context->parent()); | ||||
|     surface->setFormat(format); | ||||
|     surface->create(); | ||||
| } | ||||
| 
 | ||||
| void GLContext::MakeCurrent() { | ||||
|     context->makeCurrent(surface); | ||||
| } | ||||
| 
 | ||||
| void GLContext::DoneCurrent() { | ||||
|     context->doneCurrent(); | ||||
| } | ||||
|  |  | |||
|  | @ -7,9 +7,9 @@ | |||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <QGLWidget> | ||||
| #include <QImage> | ||||
| #include <QThread> | ||||
| #include <QWidget> | ||||
| #include <QWindow> | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
|  | @ -17,16 +17,30 @@ | |||
| class QKeyEvent; | ||||
| class QScreen; | ||||
| class QTouchEvent; | ||||
| class QOffscreenSurface; | ||||
| class QOpenGLContext; | ||||
| 
 | ||||
| class GGLWidgetInternal; | ||||
| class GMainWindow; | ||||
| class GRenderWindow; | ||||
| 
 | ||||
| class GLContext : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     explicit GLContext(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 +94,7 @@ private: | |||
|     std::mutex running_mutex; | ||||
|     std::condition_variable running_cv; | ||||
| 
 | ||||
|     GRenderWindow* render_window; | ||||
|     Frontend::GraphicsContext& core_context; | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|  | @ -104,6 +118,24 @@ signals: | |||
|     void ErrorThrown(Core::System::ResultStatus, std::string); | ||||
| }; | ||||
| 
 | ||||
| class OpenGLWindow : public QWindow { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context); | ||||
| 
 | ||||
|     ~OpenGLWindow(); | ||||
| 
 | ||||
|     void Present(); | ||||
| 
 | ||||
| protected: | ||||
|     bool event(QEvent* event) override; | ||||
|     void exposeEvent(QExposeEvent* event) override; | ||||
| 
 | ||||
| private: | ||||
|     QOpenGLContext* context; | ||||
|     QWidget* event_handler; | ||||
| }; | ||||
| 
 | ||||
| class GRenderWindow : public QWidget, public Frontend::EmuWindow { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
|  | @ -111,11 +143,11 @@ 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 BackupGeometry(); | ||||
|     void RestoreGeometry(); | ||||
|  | @ -126,6 +158,8 @@ public: | |||
| 
 | ||||
|     void closeEvent(QCloseEvent* event) override; | ||||
| 
 | ||||
|     void resizeEvent(QResizeEvent* event) override; | ||||
| 
 | ||||
|     void keyPressEvent(QKeyEvent* event) override; | ||||
|     void keyReleaseEvent(QKeyEvent* event) override; | ||||
| 
 | ||||
|  | @ -137,14 +171,14 @@ public: | |||
| 
 | ||||
|     void focusOutEvent(QFocusEvent* event) override; | ||||
| 
 | ||||
|     void OnClientAreaResized(u32 width, u32 height); | ||||
| 
 | ||||
|     void InitRenderTarget(); | ||||
| 
 | ||||
|     /// Destroy the previous run's child_widget which should also destroy the child_window
 | ||||
|     void ReleaseRenderTarget(); | ||||
| 
 | ||||
|     void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); | ||||
| 
 | ||||
| public slots: | ||||
|     void moveContext(); // overridden
 | ||||
| 
 | ||||
|     void OnEmulationStarting(EmuThread* emu_thread); | ||||
|     void OnEmulationStopping(); | ||||
|  | @ -162,10 +196,18 @@ private: | |||
| 
 | ||||
|     void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; | ||||
| 
 | ||||
|     GGLWidgetInternal* child; | ||||
|     std::unique_ptr<GraphicsContext> core_context; | ||||
| 
 | ||||
|     QByteArray geometry; | ||||
| 
 | ||||
|     /// Native window handle that backs this presentation widget
 | ||||
|     QWindow* child_window = nullptr; | ||||
| 
 | ||||
|     /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
 | ||||
|     /// put the child_window into a widget then add it to the layout. This child_widget can be
 | ||||
|     /// parented to GRenderWindow and use Qt's lifetime system
 | ||||
|     QWidget* child_widget = nullptr; | ||||
| 
 | ||||
|     EmuThread* emu_thread; | ||||
| 
 | ||||
|     /// Temporary storage of the screenshot taken
 | ||||
|  |  | |||
|  | @ -430,9 +430,9 @@ void Config::ReadRendererValues() { | |||
|     Settings::values.shaders_accurate_mul = | ||||
|         ReadSetting(QStringLiteral("shaders_accurate_mul"), false).toBool(); | ||||
|     Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool(); | ||||
|     Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool(); | ||||
|     Settings::values.resolution_factor = | ||||
|         static_cast<u16>(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt()); | ||||
|     Settings::values.vsync_enabled = ReadSetting(QStringLiteral("vsync_enabled"), false).toBool(); | ||||
|     Settings::values.use_frame_limit = | ||||
|         ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); | ||||
|     Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); | ||||
|  | @ -859,8 +859,8 @@ void Config::SaveRendererValues() { | |||
|     WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul, | ||||
|                  false); | ||||
|     WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true); | ||||
|     WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true); | ||||
|     WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1); | ||||
|     WriteSetting(QStringLiteral("vsync_enabled"), Settings::values.vsync_enabled, false); | ||||
|     WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); | ||||
|     WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) | |||
|     SetConfiguration(); | ||||
| 
 | ||||
|     ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked()); | ||||
|     ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | ||||
| 
 | ||||
|     connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] { | ||||
|         auto checked = ui->toggle_hw_renderer->isChecked(); | ||||
|         ui->hw_renderer_group->setEnabled(checked); | ||||
|  | @ -46,6 +48,7 @@ void ConfigureGraphics::SetConfiguration() { | |||
|     ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader); | ||||
|     ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul); | ||||
|     ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); | ||||
|     ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGraphics::ApplyConfiguration() { | ||||
|  | @ -53,6 +56,7 @@ void ConfigureGraphics::ApplyConfiguration() { | |||
|     Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked(); | ||||
|     Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked(); | ||||
|     Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); | ||||
|     Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked(); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGraphics::RetranslateUI() { | ||||
|  |  | |||
|  | @ -105,6 +105,25 @@ | |||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="groupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Advanced</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="toggle_vsync_new"> | ||||
|         <property name="toolTip"> | ||||
|          <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable VSync</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer name="verticalSpacer"> | ||||
|      <property name="orientation"> | ||||
|  |  | |||
|  | @ -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); | ||||
|  | @ -1019,6 +1019,9 @@ void GMainWindow::ShutdownGame() { | |||
|     UpdateWindowTitle(); | ||||
| 
 | ||||
|     game_path.clear(); | ||||
| 
 | ||||
|     // When closing the game, destroy the GLWindow to clear the context after the game is closed
 | ||||
|     render_window->ReleaseRenderTarget(); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::StoreRecentFile(const QString& filename) { | ||||
|  | @ -1869,14 +1872,33 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | |||
|     QWidget::closeEvent(event); | ||||
| } | ||||
| 
 | ||||
| static bool IsSingleFileDropEvent(QDropEvent* event) { | ||||
|     const QMimeData* mimeData = event->mimeData(); | ||||
|     return mimeData->hasUrls() && mimeData->urls().length() == 1; | ||||
| static bool IsSingleFileDropEvent(const QMimeData* mime) { | ||||
|     return mime->hasUrls() && mime->urls().length() == 1; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     if (!IsSingleFileDropEvent(event)) { | ||||
|         return; | ||||
| static const std::array<std::string, 8> AcceptedExtensions = {"cci",  "3ds", "cxi", "bin", | ||||
|                                                               "3dsx", "app", "elf", "axf"}; | ||||
| 
 | ||||
| static bool IsCorrectFileExtension(const QMimeData* mime) { | ||||
|     const QString& filename = mime->urls().at(0).toLocalFile(); | ||||
|     return std::find(AcceptedExtensions.begin(), AcceptedExtensions.end(), | ||||
|                      QFileInfo(filename).suffix().toStdString()) != AcceptedExtensions.end(); | ||||
| } | ||||
| 
 | ||||
| static bool IsAcceptableDropEvent(QDropEvent* event) { | ||||
|     return IsSingleFileDropEvent(event->mimeData()) && IsCorrectFileExtension(event->mimeData()); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::AcceptDropEvent(QDropEvent* event) { | ||||
|     if (IsAcceptableDropEvent(event)) { | ||||
|         event->setDropAction(Qt::DropAction::LinkAction); | ||||
|         event->accept(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::DropAction(QDropEvent* event) { | ||||
|     if (!IsAcceptableDropEvent(event)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const QMimeData* mime_data = event->mimeData(); | ||||
|  | @ -1891,16 +1913,19 @@ void GMainWindow::dropEvent(QDropEvent* event) { | |||
|             BootGame(filename); | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dropEvent(QDropEvent* event) { | ||||
|     DropAction(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { | ||||
|     if (IsSingleFileDropEvent(event)) { | ||||
|         event->acceptProposedAction(); | ||||
|     } | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { | ||||
|     event->acceptProposedAction(); | ||||
|     AcceptDropEvent(event); | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::ConfirmChangeGame() { | ||||
|  | @ -2050,11 +2075,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(0); | ||||
|     // 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
 | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ class QProgressBar; | |||
| class RegistersWidget; | ||||
| class Updater; | ||||
| class WaitTreeWidget; | ||||
| 
 | ||||
| namespace DiscordRPC { | ||||
| class DiscordInterface; | ||||
| } | ||||
|  | @ -69,8 +70,12 @@ public: | |||
|     GameList* game_list; | ||||
|     std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | ||||
| 
 | ||||
|     bool DropAction(QDropEvent* event); | ||||
|     void AcceptDropEvent(QDropEvent* event); | ||||
| 
 | ||||
| public slots: | ||||
|     void OnAppFocusStateChanged(Qt::ApplicationState state); | ||||
| 
 | ||||
| signals: | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -78,8 +83,8 @@ signals: | |||
|      * about to start. At this time, the core system emulation has been initialized, and all | ||||
|      * emulation handles and memory should be valid. | ||||
|      * | ||||
|      * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to | ||||
|      *      access/change emulation state). | ||||
|      * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need | ||||
|      * to access/change emulation state). | ||||
|      */ | ||||
|     void EmulationStarting(EmuThread* emu_thread); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue