summaryrefslogtreecommitdiff
path: root/src/yuzu/bootmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/bootmanager.cpp')
-rw-r--r--src/yuzu/bootmanager.cpp310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
new file mode 100644
index 000000000..eb542ad4e
--- /dev/null
+++ b/src/yuzu/bootmanager.cpp
@@ -0,0 +1,310 @@
+#include <QApplication>
+#include <QHBoxLayout>
+#include <QKeyEvent>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+// Required for screen DPI information
+#include <QScreen>
+#include <QWindow>
+#endif
+
+#include "citra_qt/bootmanager.h"
+#include "common/microprofile.h"
+#include "common/scm_rev.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/framebuffer_layout.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"
+
+EmuThread::EmuThread(GRenderWindow* render_window)
+ : exec_step(false), running(false), stop_run(false), render_window(render_window) {}
+
+void EmuThread::run() {
+ render_window->MakeCurrent();
+
+ MicroProfileOnThreadCreate("EmuThread");
+
+ stop_run = false;
+
+ // holds whether the cpu was running during the last iteration,
+ // so that the DebugModeLeft signal can be emitted before the
+ // next execution step
+ bool was_active = false;
+ while (!stop_run) {
+ if (running) {
+ if (!was_active)
+ emit DebugModeLeft();
+
+ Core::System::ResultStatus result = Core::System::GetInstance().RunLoop();
+ if (result != Core::System::ResultStatus::Success) {
+ emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
+ }
+
+ was_active = running || exec_step;
+ if (!was_active && !stop_run)
+ emit DebugModeEntered();
+ } else if (exec_step) {
+ if (!was_active)
+ emit DebugModeLeft();
+
+ exec_step = false;
+ Core::System::GetInstance().SingleStep();
+ emit DebugModeEntered();
+ yieldCurrentThread();
+
+ was_active = false;
+ } else {
+ std::unique_lock<std::mutex> lock(running_mutex);
+ running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
+ }
+ }
+
+ // Shutdown the core emulation
+ Core::System::GetInstance().Shutdown();
+
+#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) {
+
+ std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name,
+ Common::g_scm_branch, Common::g_scm_desc);
+ setWindowTitle(QString::fromStdString(window_title));
+
+ InputCommon::Init();
+ Network::Init();
+}
+
+GRenderWindow::~GRenderWindow() {
+ InputCommon::Shutdown();
+ Network::Shutdown();
+}
+
+void GRenderWindow::moveContext() {
+ DoneCurrent();
+// We need to move GL context to the swapping thread in Qt5
+#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
+ // 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);
+#endif
+}
+
+void GRenderWindow::SwapBuffers() {
+#if !defined(QT_NO_DEBUG)
+ // Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
+ // since the last time you called swapBuffers. This presumably means something if you're using
+ // QGLWidget the "regular" way, but in our multi-threaded use case is harmless since we never
+ // call doneCurrent in this thread.
+ child->makeCurrent();
+#endif
+ child->swapBuffers();
+}
+
+void GRenderWindow::MakeCurrent() {
+ child->makeCurrent();
+}
+
+void GRenderWindow::DoneCurrent() {
+ child->doneCurrent();
+}
+
+void GRenderWindow::PollEvents() {}
+
+// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
+//
+// Older versions get the window size (density independent pixels),
+// and hence, do not support DPI scaling ("retina" displays).
+// The result will be a viewport that is smaller than the extent of the window.
+void GRenderWindow::OnFramebufferSizeChanged() {
+ // Screen changes potentially incur a change in screen DPI, hence we should update the
+ // framebuffer size
+ qreal pixelRatio = windowPixelRatio();
+ unsigned width = child->QPaintDevice::width() * pixelRatio;
+ unsigned height = child->QPaintDevice::height() * pixelRatio;
+ UpdateCurrentFramebufferLayout(width, height);
+}
+
+void GRenderWindow::BackupGeometry() {
+ geometry = ((QGLWidget*)this)->saveGeometry();
+}
+
+void GRenderWindow::RestoreGeometry() {
+ // We don't want to back up the geometry here (obviously)
+ QWidget::restoreGeometry(geometry);
+}
+
+void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
+ // Make sure users of this class don't need to deal with backing up the geometry themselves
+ QWidget::restoreGeometry(geometry);
+ BackupGeometry();
+}
+
+QByteArray GRenderWindow::saveGeometry() {
+ // If we are a top-level widget, store the current geometry
+ // otherwise, store the last backup
+ if (parent() == nullptr)
+ return ((QGLWidget*)this)->saveGeometry();
+ else
+ return geometry;
+}
+
+qreal GRenderWindow::windowPixelRatio() {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+ // windowHandle() might not be accessible until the window is displayed to screen.
+ return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
+#else
+ return 1.0f;
+#endif
+}
+
+void GRenderWindow::closeEvent(QCloseEvent* event) {
+ emit Closed();
+ 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) {
+ auto pos = event->pos();
+ if (event->button() == Qt::LeftButton) {
+ qreal pixelRatio = windowPixelRatio();
+ this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
+ static_cast<unsigned>(pos.y() * pixelRatio));
+ } else if (event->button() == Qt::RightButton) {
+ InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ }
+}
+
+void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
+ auto pos = event->pos();
+ qreal pixelRatio = windowPixelRatio();
+ this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
+ std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
+ InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
+}
+
+void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
+ if (event->button() == Qt::LeftButton)
+ this->TouchReleased();
+ else if (event->button() == Qt::RightButton)
+ InputCommon::GetMotionEmu()->EndTilt();
+}
+
+void GRenderWindow::focusOutEvent(QFocusEvent* event) {
+ QWidget::focusOutEvent(event);
+ InputCommon::GetKeyboard()->ReleaseAllKeys();
+}
+
+void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
+ NotifyClientAreaSizeChanged(std::make_pair(width, height));
+}
+
+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.use_vsync);
+
+ // 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(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
+ 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::OnMinimalClientAreaChangeRequest(
+ const std::pair<unsigned, unsigned>& minimal_size) {
+ setMinimumSize(minimal_size.first, minimal_size.second);
+}
+
+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);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+ // windowHandle() is not initialized until the Window is shown, so we connect it here.
+ connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this,
+ SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection);
+#endif
+}