diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp index e4091ea2ac..23f65c0f22 100644 --- a/pcsx2-qt/DisplayWidget.cpp +++ b/pcsx2-qt/DisplayWidget.cpp @@ -17,6 +17,7 @@ #include "DisplayWidget.h" #include "EmuThread.h" +#include "MainWindow.h" #include "QtHost.h" #include "pcsx2/GS/GSIntrin.h" // _BitScanForward @@ -242,7 +243,13 @@ bool DisplayWidget::event(QEvent* event) case QEvent::Close: { - emit windowClosedEvent(); + if (!g_main_window->requestShutdown()) + { + // abort the window close + event->ignore(); + return true; + } + QWidget::event(event); return true; } @@ -317,18 +324,19 @@ DisplayWidget* DisplayContainer::removeDisplayWidget() bool DisplayContainer::event(QEvent* event) { + if (event->type() == QEvent::Close && !g_main_window->requestShutdown()) + { + // abort the window close + event->ignore(); + return true; + } + const bool res = QStackedWidget::event(event); if (!m_display_widget) return res; switch (event->type()) { - case QEvent::Close: - { - emit m_display_widget->windowClosedEvent(); - } - break; - case QEvent::WindowStateChange: { if (static_cast(event)->oldState() & Qt::WindowMinimized) diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h index 7ed34295b4..1df5577551 100644 --- a/pcsx2-qt/DisplayWidget.h +++ b/pcsx2-qt/DisplayWidget.h @@ -42,7 +42,6 @@ Q_SIGNALS: void windowFocusEvent(); void windowResizedEvent(int width, int height, float scale); void windowRestoredEvent(); - void windowClosedEvent(); void windowKeyEvent(int key_code, bool pressed); void windowMouseMoveEvent(int x, int y); void windowMouseButtonEvent(int button, bool pressed); diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index a235e265f5..73da1d489b 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -108,6 +108,7 @@ void EmuThread::startVM(std::shared_ptr boot_params) // create the display, this may take a while... m_is_fullscreen = boot_params->fullscreen.value_or(QtHost::GetBaseBoolSettingValue("UI", "StartFullscreen", false)); m_is_rendering_to_main = QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true); + m_is_surfaceless = false; if (!VMManager::Initialize(*boot_params)) return; @@ -134,6 +135,10 @@ void EmuThread::setVMPaused(bool paused) return; } + // if we were surfaceless (view->game list, system->unpause), get our display widget back + if (m_is_surfaceless) + setSurfaceless(false); + VMManager::SetPaused(paused); } @@ -346,6 +351,23 @@ void EmuThread::setFullscreen(bool fullscreen) GetMTGS().WaitGS(); } +void EmuThread::setSurfaceless(bool surfaceless) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "setSurfaceless", Qt::QueuedConnection, Q_ARG(bool, surfaceless)); + return; + } + + if (!VMManager::HasValidVM() || m_is_surfaceless == surfaceless) + return; + + // This will call back to us on the MTGS thread. + m_is_surfaceless = surfaceless; + GetMTGS().UpdateDisplayWindow(); + GetMTGS().WaitGS(); +} + void EmuThread::applySettings() { if (!isOnEmuThread()) @@ -545,7 +567,6 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget) connect(widget, &DisplayWidget::windowFocusEvent, this, &EmuThread::onDisplayWindowFocused); connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized); // connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow); - connect(widget, &DisplayWidget::windowClosedEvent, []() { g_main_window->requestShutdown(); }); connect(widget, &DisplayWidget::windowKeyEvent, this, &EmuThread::onDisplayWindowKeyEvent); connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &EmuThread::onDisplayWindowMouseMoveEvent); connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &EmuThread::onDisplayWindowMouseButtonEvent); @@ -590,8 +611,8 @@ void EmuThread::updateDisplay() display->DoneRenderContextCurrent(); // but we should get it back after this call - DisplayWidget* widget = onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main); - if (!widget || !display->MakeRenderContextCurrent()) + onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless); + if (!display->MakeRenderContextCurrent()) { pxFailRel("Failed to recreate context after updating"); return; @@ -851,9 +872,11 @@ void Host::RunOnCPUThread(std::function function, bool block /* = false Q_ARG(const std::function&, std::move(function))); } -ScopedVMPause::ScopedVMPause(bool was_paused) - : m_was_paused(was_paused) +ScopedVMPause::ScopedVMPause(bool was_paused, bool was_fullscreen) + : m_was_paused(was_paused), m_was_fullscreen(was_fullscreen) { + if (was_fullscreen) + g_emu_thread->setFullscreen(false); if (!m_was_paused) g_emu_thread->setVMPaused(true); } @@ -862,6 +885,8 @@ ScopedVMPause::~ScopedVMPause() { if (!m_was_paused) g_emu_thread->setVMPaused(false); + if (m_was_fullscreen) + g_emu_thread->setFullscreen(true); } alignas(16) static SysMtgsThread s_mtgs_thread; diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h index b13c1e223f..5386a6f32c 100644 --- a/pcsx2-qt/EmuThread.h +++ b/pcsx2-qt/EmuThread.h @@ -66,6 +66,7 @@ public Q_SLOTS: void saveStateToSlot(qint32 slot); void toggleFullscreen(); void setFullscreen(bool fullscreen); + void setSurfaceless(bool surfaceless); void applySettings(); void reloadGameSettings(); void toggleSoftwareRendering(); @@ -81,7 +82,7 @@ public Q_SLOTS: Q_SIGNALS: DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main); - DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main); + DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless); void onResizeDisplayRequested(qint32 width, qint32 height); void onDestroyDisplayRequested(); @@ -157,6 +158,7 @@ private: bool m_verbose_status = false; bool m_is_rendering_to_main = false; bool m_is_fullscreen = false; + bool m_is_surfaceless = false; float m_last_speed = 0.0f; float m_last_game_fps = 0.0f; @@ -171,11 +173,15 @@ private: class ScopedVMPause { public: - ScopedVMPause(bool was_paused); + ScopedVMPause(bool was_paused, bool was_fullscreen); ~ScopedVMPause(); + __fi bool WasPaused() const { return m_was_paused; } + __fi bool WasFullscreen() const { return m_was_fullscreen; } + private: bool m_was_paused; + bool m_was_fullscreen; }; extern EmuThread* g_emu_thread; diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index d9b5a4124f..1cfaa18071 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -104,11 +104,10 @@ void MainWindow::setupAdditionalUi() m_ui.actionViewStatusBar->setChecked(status_bar_visible); m_ui.statusBar->setVisible(status_bar_visible); - m_game_list_widget = new GameListWidget(m_ui.mainContainer); + m_game_list_widget = new GameListWidget(this); m_game_list_widget->initialize(); - m_ui.mainContainer->insertWidget(0, m_game_list_widget); - m_ui.mainContainer->setCurrentIndex(0); m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles()); + setCentralWidget(m_game_list_widget); m_status_progress_widget = new QProgressBar(m_ui.statusBar); m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); @@ -578,6 +577,9 @@ void MainWindow::updateEmulationActions(bool starting, bool running) m_ui.actionViewGameProperties->setEnabled(running); m_game_list_widget->setDisabled(starting && !running); + + if (!starting && !running) + m_ui.actionPause->setChecked(false); } void MainWindow::updateStatusBarWidgetVisibility() @@ -603,26 +605,28 @@ void MainWindow::updateStatusBarWidgetVisibility() void MainWindow::updateWindowTitle() { - QString title; - if (!m_vm_valid || m_current_game_name.isEmpty()) - { #if defined(_DEBUG) - title = QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV); + QString main_title(QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV)); + QString display_title(QStringLiteral("%1 [Debug]").arg(m_current_game_name)); #else - title = QStringLiteral("PCSX2 %1").arg(GIT_REV); + QString main_title(QStringLiteral("PCSX2 %1").arg(GIT_REV)); + QString display_title(m_current_game_name); #endif - } - else - { -#if defined(_DEBUG) - title = QStringLiteral("%1 [Debug]").arg(m_current_game_name); -#else - title = m_current_game_name; -#endif - } - if (windowTitle() != title) - setWindowTitle(title); + if (!m_vm_valid || m_current_game_name.isEmpty()) + display_title = main_title; + else if (isRenderingToMain()) + main_title = display_title; + + if (windowTitle() != main_title) + setWindowTitle(main_title); + + if (m_display_widget && !isRenderingToMain()) + { + QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); + if (container->windowTitle() != display_title) + container->setWindowTitle(display_title); + } } void MainWindow::setProgressBar(int current, int total) @@ -648,38 +652,64 @@ void MainWindow::clearProgressBar() bool MainWindow::isShowingGameList() const { - return m_ui.mainContainer->currentIndex() == 0; + return (centralWidget() == m_game_list_widget); +} + +bool MainWindow::isRenderingFullscreen() const +{ + HostDisplay* display = Host::GetHostDisplay(); + if (!display || !m_display_widget) + return false; + + return (m_display_widget->parent() != this && (m_display_widget->isFullScreen() || display->IsFullscreen())); +} + +bool MainWindow::isRenderingToMain() const +{ + return (m_display_widget && m_display_widget->parent() == this); } void MainWindow::switchToGameListView() { - if ((m_display_widget && !m_display_widget->parent()) || m_ui.mainContainer->currentIndex() == 0) + if (centralWidget() == m_game_list_widget) + { + m_game_list_widget->setFocus(); return; + } if (m_vm_valid) { - m_was_focused_on_container_switch = m_vm_paused; + m_was_paused_on_surface_loss = m_vm_paused; if (!m_vm_paused) g_emu_thread->setVMPaused(true); + + // switch to surfaceless. we have to wait until the display widget is gone before we swap over. + g_emu_thread->setSurfaceless(true); + while (m_display_widget) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); } - m_ui.mainContainer->setCurrentIndex(0); + pxAssertMsg(!centralWidget(), "Should not have a central widget at game list switch time"); + takeCentralWidget(); + setCentralWidget(m_game_list_widget); + m_game_list_widget->setVisible(true); m_game_list_widget->setFocus(); } void MainWindow::switchToEmulationView() { - if (!m_display_widget || !m_display_widget->parent() || m_ui.mainContainer->currentIndex() == 1) + if (!m_vm_valid || (m_display_widget && centralWidget() == m_display_widget)) return; - if (m_vm_valid) - { - m_ui.mainContainer->setCurrentIndex(1); - if (m_vm_paused && !m_was_focused_on_container_switch) - g_emu_thread->setVMPaused(false); - } + // we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out. + g_emu_thread->setSurfaceless(false); - m_display_widget->setFocus(); + // resume if we weren't paused at switch time + if (m_vm_paused && !m_was_paused_on_surface_loss) + g_emu_thread->setVMPaused(false); + + if (m_display_widget) + m_display_widget->setFocus(); } void MainWindow::refreshGameList(bool invalidate_cache) @@ -710,8 +740,8 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav // only confirm on UI thread because we need to display a msgbox if (allow_confirm && !GSDumpReplayer::IsReplayingDump() && QtHost::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true)) { - ScopedVMPause pauser(m_vm_paused); - if (QMessageBox::question(g_main_window, tr("Confirm Shutdown"), + ScopedVMPause pauser(m_vm_paused, isRenderingFullscreen()); + if (QMessageBox::question(m_display_widget, tr("Confirm Shutdown"), tr("Are you sure you want to shut down the virtual machine?\n\nAll unsaved progress will be lost.")) != QMessageBox::Yes) { return false; @@ -882,7 +912,7 @@ void MainWindow::onStartBIOSActionTriggered() void MainWindow::onChangeDiscFromFileActionTriggered() { - ScopedVMPause pauser(m_vm_paused); + ScopedVMPause pauser(m_vm_paused, isRenderingFullscreen()); QString filename = QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr); if (filename.isEmpty()) @@ -1114,6 +1144,8 @@ void MainWindow::closeEvent(QCloseEvent* event) DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) { + DevCon.WriteLn("createDisplay(%u, %u)", static_cast(fullscreen), static_cast(render_to_main)); + HostDisplay* host_display = Host::GetHostDisplay(); if (!host_display) return nullptr; @@ -1131,12 +1163,15 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) } else { - m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); + m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr); container = m_display_widget; } - container->setWindowTitle(windowTitle()); - container->setWindowIcon(windowIcon()); + if (fullscreen || !render_to_main) + { + container->setWindowTitle(windowTitle()); + container->setWindowIcon(windowIcon()); + } if (fullscreen) { @@ -1152,8 +1187,9 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) } else { - m_ui.mainContainer->insertWidget(1, container); - switchToEmulationView(); + m_game_list_widget->setVisible(false); + takeCentralWidget(); + setCentralWidget(m_display_widget); } // we need the surface visible.. this might be able to be replaced with something else @@ -1180,26 +1216,32 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) if (is_exclusive_fullscreen) setDisplayFullscreen(fullscreen_mode); + updateWindowTitle(); + m_display_widget->setFocus(); + host_display->DoneRenderContextCurrent(); return m_display_widget; } -DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) +DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless) { + DevCon.WriteLn("updateDisplay(%u, %u, %u)", static_cast(fullscreen), static_cast(render_to_main), static_cast(surfaceless)); + HostDisplay* host_display = Host::GetHostDisplay(); QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); - const bool is_fullscreen = container->isFullScreen(); - const bool is_rendering_to_main = (!is_fullscreen && container->parent()); + const bool is_fullscreen = (container && container->isFullScreen()); + const bool is_rendering_to_main = (!is_fullscreen && container && container->parent()); const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen()); - if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) + const bool changing_surfaceless = (!m_display_widget != surfaceless); + if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless) return m_display_widget; // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off. // .. except on Wayland, where everything tends to break if you don't recreate. const bool has_container = (m_display_container != nullptr); const bool needs_container = DisplayContainer::IsNeeded(fullscreen, render_to_main); - if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container) + if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container && !changing_surfaceless) { Console.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed")); if (host_display->IsFullscreen()) @@ -1223,6 +1265,10 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) destroyDisplayWidget(); + // if we're going to surfaceless, we're done here + if (surfaceless) + return nullptr; + if (DisplayContainer::IsNeeded(fullscreen, render_to_main)) { m_display_container = new DisplayContainer(); @@ -1232,12 +1278,22 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) } else { - m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); + m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr); container = m_display_widget; } - container->setWindowTitle(windowTitle()); - container->setWindowIcon(windowIcon()); + if (fullscreen || !render_to_main) + { + container->setWindowTitle(windowTitle()); + container->setWindowIcon(windowIcon()); + + // make sure the game list widget is still visible + if (centralWidget() != m_game_list_widget && !fullscreen) + { + setCentralWidget(m_game_list_widget); + m_game_list_widget->setVisible(true); + } + } if (fullscreen) { @@ -1253,8 +1309,10 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) } else { - m_ui.mainContainer->insertWidget(1, container); - switchToEmulationView(); + m_game_list_widget->setVisible(false); + takeCentralWidget(); + setCentralWidget(m_display_widget); + m_display_widget->setFocus(); } // we need the surface visible.. this might be able to be replaced with something else @@ -1276,6 +1334,7 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) if (is_exclusive_fullscreen) setDisplayFullscreen(fullscreen_mode); + updateWindowTitle(); m_display_widget->setFocus(); QSignalBlocker blocker(m_ui.actionFullscreen); @@ -1308,11 +1367,20 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height) void MainWindow::destroyDisplay() { destroyDisplayWidget(); + + // switch back to game list view, we're not going back to display, so we can't use switchToGameListView(). + if (centralWidget() != m_game_list_widget) + { + takeCentralWidget(); + setCentralWidget(m_game_list_widget); + m_game_list_widget->setVisible(true); + m_game_list_widget->setFocus(); + } } void MainWindow::focusDisplayWidget() { - if (m_ui.mainContainer->currentIndex() != 1) + if (!m_display_widget || centralWidget() != m_display_widget) return; m_display_widget->setFocus(); @@ -1355,18 +1423,20 @@ void MainWindow::destroyDisplayWidget() if (m_display_container) m_display_container->removeDisplayWidget(); - if (m_display_widget->parent()) + if (m_display_widget == centralWidget()) + takeCentralWidget(); + + if (m_display_widget) { - m_ui.mainContainer->removeWidget(m_display_widget); - m_ui.mainContainer->setCurrentIndex(0); - m_game_list_widget->setFocus(); + m_display_widget->deleteLater(); + m_display_widget = nullptr; } - delete m_display_widget; - m_display_widget = nullptr; - - delete m_display_container; - m_display_container = nullptr; + if (m_display_container) + { + m_display_container->deleteLater(); + m_display_container = nullptr; + } } void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 0ff57c8caf..b76b216680 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -62,7 +62,7 @@ public Q_SLOTS: private Q_SLOTS: DisplayWidget* createDisplay(bool fullscreen, bool render_to_main); - DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main); + DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless); void displayResizeRequested(qint32 width, qint32 height); void destroyDisplay(); void focusDisplayWidget(); @@ -134,6 +134,8 @@ private: void clearProgressBar(); bool isShowingGameList() const; + bool isRenderingFullscreen() const; + bool isRenderingToMain() const; void switchToGameListView(); void switchToEmulationView(); @@ -181,7 +183,7 @@ private: bool m_vm_valid = false; bool m_vm_paused = false; bool m_save_states_invalidated = false; - bool m_was_focused_on_container_switch = false; + bool m_was_paused_on_surface_loss = false; QString m_last_fps_status; }; diff --git a/pcsx2-qt/MainWindow.ui b/pcsx2-qt/MainWindow.ui index b43d4a1b1c..601e3318c0 100644 --- a/pcsx2-qt/MainWindow.ui +++ b/pcsx2-qt/MainWindow.ui @@ -20,13 +20,6 @@ :/icons/AppIcon.png:/icons/AppIcon.png - - - 0 - - - - diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 1b3913514a..d1c51b2b37 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -117,6 +117,7 @@ void GSinitConfig() void GSshutdown() { +#ifndef PCSX2_CORE if (g_gs_renderer) { g_gs_renderer->Destroy(); @@ -129,6 +130,7 @@ void GSshutdown() } Host::ReleaseHostDisplay(); +#endif #ifdef _WIN32 if (SUCCEEDED(s_hr))