bootmanager.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #include <QApplication>
  2. #include <QHBoxLayout>
  3. #include <QKeyEvent>
  4. #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
  5. // Required for screen DPI information
  6. #include <QScreen>
  7. #include <QWindow>
  8. #endif
  9. #include "citra_qt/bootmanager.h"
  10. #include "common/microprofile.h"
  11. #include "common/scm_rev.h"
  12. #include "common/string_util.h"
  13. #include "core/core.h"
  14. #include "core/frontend/key_map.h"
  15. #include "video_core/debug_utils/debug_utils.h"
  16. #include "video_core/video_core.h"
  17. EmuThread::EmuThread(GRenderWindow* render_window)
  18. : exec_step(false), running(false), stop_run(false), render_window(render_window) {}
  19. void EmuThread::run() {
  20. render_window->MakeCurrent();
  21. MicroProfileOnThreadCreate("EmuThread");
  22. stop_run = false;
  23. // holds whether the cpu was running during the last iteration,
  24. // so that the DebugModeLeft signal can be emitted before the
  25. // next execution step
  26. bool was_active = false;
  27. while (!stop_run) {
  28. if (running) {
  29. if (!was_active)
  30. emit DebugModeLeft();
  31. Core::System::GetInstance().RunLoop();
  32. was_active = running || exec_step;
  33. if (!was_active && !stop_run)
  34. emit DebugModeEntered();
  35. } else if (exec_step) {
  36. if (!was_active)
  37. emit DebugModeLeft();
  38. exec_step = false;
  39. Core::System::GetInstance().SingleStep();
  40. emit DebugModeEntered();
  41. yieldCurrentThread();
  42. was_active = false;
  43. } else {
  44. std::unique_lock<std::mutex> lock(running_mutex);
  45. running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
  46. }
  47. }
  48. // Shutdown the core emulation
  49. Core::System::GetInstance().Shutdown();
  50. #if MICROPROFILE_ENABLED
  51. MicroProfileOnThreadExit();
  52. #endif
  53. render_window->moveContext();
  54. }
  55. // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
  56. // context.
  57. // The corresponding functionality is handled in EmuThread instead
  58. class GGLWidgetInternal : public QGLWidget {
  59. public:
  60. GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
  61. : QGLWidget(fmt, parent), parent(parent) {}
  62. void paintEvent(QPaintEvent* ev) override {
  63. if (do_painting) {
  64. QPainter painter(this);
  65. }
  66. }
  67. void resizeEvent(QResizeEvent* ev) override {
  68. parent->OnClientAreaResized(ev->size().width(), ev->size().height());
  69. parent->OnFramebufferSizeChanged();
  70. }
  71. void DisablePainting() {
  72. do_painting = false;
  73. }
  74. void EnablePainting() {
  75. do_painting = true;
  76. }
  77. private:
  78. GRenderWindow* parent;
  79. bool do_painting;
  80. };
  81. GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
  82. : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) {
  83. std::string window_title =
  84. Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
  85. setWindowTitle(QString::fromStdString(window_title));
  86. keyboard_id = KeyMap::NewDeviceId();
  87. ReloadSetKeymaps();
  88. }
  89. void GRenderWindow::moveContext() {
  90. DoneCurrent();
  91. // We need to move GL context to the swapping thread in Qt5
  92. #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
  93. // If the thread started running, move the GL Context to the new thread. Otherwise, move it
  94. // back.
  95. auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
  96. ? emu_thread
  97. : qApp->thread();
  98. child->context()->moveToThread(thread);
  99. #endif
  100. }
  101. void GRenderWindow::SwapBuffers() {
  102. #if !defined(QT_NO_DEBUG)
  103. // Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
  104. // since the last time you called swapBuffers. This presumably means something if you're using
  105. // QGLWidget the "regular" way, but in our multi-threaded use case is harmless since we never
  106. // call doneCurrent in this thread.
  107. child->makeCurrent();
  108. #endif
  109. child->swapBuffers();
  110. }
  111. void GRenderWindow::MakeCurrent() {
  112. child->makeCurrent();
  113. }
  114. void GRenderWindow::DoneCurrent() {
  115. child->doneCurrent();
  116. }
  117. void GRenderWindow::PollEvents() {}
  118. // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
  119. //
  120. // Older versions get the window size (density independent pixels),
  121. // and hence, do not support DPI scaling ("retina" displays).
  122. // The result will be a viewport that is smaller than the extent of the window.
  123. void GRenderWindow::OnFramebufferSizeChanged() {
  124. // Screen changes potentially incur a change in screen DPI, hence we should update the
  125. // framebuffer size
  126. qreal pixelRatio = windowPixelRatio();
  127. unsigned width = child->QPaintDevice::width() * pixelRatio;
  128. unsigned height = child->QPaintDevice::height() * pixelRatio;
  129. UpdateCurrentFramebufferLayout(width, height);
  130. }
  131. void GRenderWindow::BackupGeometry() {
  132. geometry = ((QGLWidget*)this)->saveGeometry();
  133. }
  134. void GRenderWindow::RestoreGeometry() {
  135. // We don't want to back up the geometry here (obviously)
  136. QWidget::restoreGeometry(geometry);
  137. }
  138. void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
  139. // Make sure users of this class don't need to deal with backing up the geometry themselves
  140. QWidget::restoreGeometry(geometry);
  141. BackupGeometry();
  142. }
  143. QByteArray GRenderWindow::saveGeometry() {
  144. // If we are a top-level widget, store the current geometry
  145. // otherwise, store the last backup
  146. if (parent() == nullptr)
  147. return ((QGLWidget*)this)->saveGeometry();
  148. else
  149. return geometry;
  150. }
  151. qreal GRenderWindow::windowPixelRatio() {
  152. #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
  153. // windowHandle() might not be accessible until the window is displayed to screen.
  154. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
  155. #else
  156. return 1.0f;
  157. #endif
  158. }
  159. void GRenderWindow::closeEvent(QCloseEvent* event) {
  160. emit Closed();
  161. QWidget::closeEvent(event);
  162. }
  163. void GRenderWindow::keyPressEvent(QKeyEvent* event) {
  164. KeyMap::PressKey(*this, {event->key(), keyboard_id});
  165. }
  166. void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
  167. KeyMap::ReleaseKey(*this, {event->key(), keyboard_id});
  168. }
  169. void GRenderWindow::mousePressEvent(QMouseEvent* event) {
  170. if (event->button() == Qt::LeftButton) {
  171. auto pos = event->pos();
  172. qreal pixelRatio = windowPixelRatio();
  173. this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
  174. static_cast<unsigned>(pos.y() * pixelRatio));
  175. }
  176. }
  177. void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
  178. auto pos = event->pos();
  179. qreal pixelRatio = windowPixelRatio();
  180. this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
  181. std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
  182. }
  183. void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
  184. if (event->button() == Qt::LeftButton)
  185. this->TouchReleased();
  186. }
  187. void GRenderWindow::ReloadSetKeymaps() {
  188. KeyMap::ClearKeyMapping(keyboard_id);
  189. for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
  190. KeyMap::SetKeyMapping(
  191. {Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id},
  192. KeyMap::mapping_targets[i]);
  193. }
  194. }
  195. void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
  196. NotifyClientAreaSizeChanged(std::make_pair(width, height));
  197. }
  198. void GRenderWindow::InitRenderTarget() {
  199. if (child) {
  200. delete child;
  201. }
  202. if (layout()) {
  203. delete layout();
  204. }
  205. // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
  206. // WA_DontShowOnScreen, WA_DeleteOnClose
  207. QGLFormat fmt;
  208. fmt.setVersion(3, 3);
  209. fmt.setProfile(QGLFormat::CoreProfile);
  210. fmt.setSwapInterval(Settings::values.use_vsync);
  211. // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
  212. fmt.setOption(QGL::NoDeprecatedFunctions);
  213. child = new GGLWidgetInternal(fmt, this);
  214. QBoxLayout* layout = new QHBoxLayout(this);
  215. resize(VideoCore::kScreenTopWidth,
  216. VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
  217. layout->addWidget(child);
  218. layout->setMargin(0);
  219. setLayout(layout);
  220. OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
  221. OnFramebufferSizeChanged();
  222. NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height()));
  223. BackupGeometry();
  224. }
  225. void GRenderWindow::OnMinimalClientAreaChangeRequest(
  226. const std::pair<unsigned, unsigned>& minimal_size) {
  227. setMinimumSize(minimal_size.first, minimal_size.second);
  228. }
  229. void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
  230. this->emu_thread = emu_thread;
  231. child->DisablePainting();
  232. }
  233. void GRenderWindow::OnEmulationStopping() {
  234. emu_thread = nullptr;
  235. child->EnablePainting();
  236. }
  237. void GRenderWindow::showEvent(QShowEvent* event) {
  238. QWidget::showEvent(event);
  239. #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
  240. // windowHandle() is not initialized until the Window is shown, so we connect it here.
  241. connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this,
  242. SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection);
  243. #endif
  244. }