From 6b969a0b94f3e6926113a082c5922144954c831b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 18 Jul 2025 16:14:29 +1000 Subject: [PATCH] Qt: Refactor and remove multiple sources of truth for render-to-main --- src/duckstation-qt/displaywidget.h | 5 ++ src/duckstation-qt/mainwindow.cpp | 108 +++++++++++++++++------------ src/duckstation-qt/mainwindow.h | 7 +- src/duckstation-qt/qthost.cpp | 60 +++++++--------- src/duckstation-qt/qthost.h | 15 ++-- 5 files changed, 105 insertions(+), 90 deletions(-) diff --git a/src/duckstation-qt/displaywidget.h b/src/duckstation-qt/displaywidget.h index 6ae21dec9..ac49fcbae 100644 --- a/src/duckstation-qt/displaywidget.h +++ b/src/duckstation-qt/displaywidget.h @@ -25,6 +25,9 @@ public: explicit DisplayWidget(QWidget* parent); ~DisplayWidget(); + ALWAYS_INLINE const char* windowPositionKey() const { return m_window_position_key; } + ALWAYS_INLINE void setWindowPositionKey(const char* key) { m_window_position_key = key; } + QPaintEngine* paintEngine() const override; int scaledWindowWidth() const; @@ -67,6 +70,8 @@ private: u32 m_last_window_width = 0; u32 m_last_window_height = 0; float m_last_window_scale = 1.0f; + + const char* m_window_position_key = nullptr; }; class DisplayContainer final : public QStackedWidget diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 5ac193b5e..a915235d5 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -251,13 +251,13 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr #endif std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, bool fullscreen, - bool exclusive_fullscreen, bool render_to_main, - bool surfaceless, bool use_main_window_pos, Error* error) + bool exclusive_fullscreen, bool surfaceless, Error* error) { - DEV_LOG("acquireRenderWindow() fullscreen={} exclusive_fullscreen={}, render_to_main={} surfaceless={} " - "use_main_window_pos={}", - fullscreen ? "true" : "false", exclusive_fullscreen ? "true" : "false", render_to_main ? "true" : "false", - surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false"); + const bool render_to_main = + QtHost::CanRenderToMainWindow() && !fullscreen && (s_system_locked.load(std::memory_order_relaxed) == 0); + + DEV_LOG("acquireRenderWindow() fullscreen={} exclusive_fullscreen={}, render_to_main={}, surfaceless={} ", fullscreen, + exclusive_fullscreen, render_to_main, surfaceless); QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); @@ -279,6 +279,9 @@ std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed")); m_exclusive_fullscreen_requested = exclusive_fullscreen; + // ensure it's resizable when changing size, we'll fix it up later in updateWindowState() + QtUtils::SetWindowResizeable(container, true); + // since we don't destroy the display widget, we need to save it here if (!is_fullscreen && !is_rendering_to_main) saveDisplayWindowGeometryToConfig(); @@ -290,10 +293,7 @@ std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, else { container->showNormal(); - if (use_main_window_pos) - container->setGeometry(geometry()); - else - restoreDisplayWindowGeometryFromConfig(); + restoreDisplayWindowGeometryFromConfig(); } updateDisplayWidgetCursor(); @@ -309,7 +309,7 @@ std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, std::optional wi; if (!surfaceless) { - createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); + createDisplayWidget(fullscreen, render_to_main); wi = m_display_widget->getWindowInfo(render_api, error); if (!wi.has_value()) @@ -343,7 +343,7 @@ bool MainWindow::hasDisplayWidget() const return m_display_widget != nullptr; } -void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos) +void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main) { // If we're rendering to main and were hidden (e.g. coming back from fullscreen), // make sure we're visible before trying to add ourselves. Otherwise Wayland breaks. @@ -384,10 +384,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool } else if (!render_to_main) { - if (use_main_window_pos) - container->setGeometry(geometry()); - else - restoreDisplayWindowGeometryFromConfig(); + restoreDisplayWindowGeometryFromConfig(); container->showNormal(); if (s_disable_window_rounded_corners) @@ -441,7 +438,7 @@ void MainWindow::releaseRenderWindow() // Now we can safely destroy the display window. destroyDisplayWidget(true); updateWindowTitle(); - updateWindowState(false); + updateWindowState(); } void MainWindow::destroyDisplayWidget(bool show_game_list) @@ -809,7 +806,7 @@ void MainWindow::recreate() { g_emu_thread->setSurfaceless(false); if (was_fullscreen) - g_emu_thread->setFullscreen(true, true); + g_emu_thread->setFullscreen(true); g_main_window->updateEmulationActions(false, s_system_valid, s_achievements_hardcore_mode); g_main_window->onFullscreenUIStartedOrStopped(s_fullscreen_ui_started); } @@ -2035,22 +2032,20 @@ void MainWindow::updateWindowTitle() g_log_window->updateWindowTitle(); } -void MainWindow::updateWindowState(bool force_visible) +void MainWindow::updateWindowState() { // Skip all of this when we're closing, since we don't want to make ourselves visible and cancel it. if (m_is_closing) return; - const bool hide_window = shouldHideMainWindow(); - const bool disable_resize = (Host::GetBoolSettingValue("Main", "DisableWindowResize", false) && wantsDisplayWidget()); + const bool visible = !shouldHideMainWindow(); + const bool resizeable = (!Host::GetBoolSettingValue("Main", "DisableWindowResize", false) || !wantsDisplayWidget() || + isRenderingFullscreen()); - // Need to test both valid and display widget because of startup (vm invalid while window is created). - const bool visible = force_visible || !hide_window; if (isVisible() != visible) setVisible(visible); // No point changing realizability if we're not visible. - const bool resizeable = force_visible || !disable_resize; if (visible) QtUtils::SetWindowResizeable(this, resizeable); @@ -2108,12 +2103,12 @@ bool MainWindow::shouldHideMouseCursor() const bool MainWindow::shouldHideMainWindow() const { // CanRenderToMain check is for temporary unfullscreens. - return !isRenderingToMain() && wantsDisplayWidget() && - ((Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && - Host::GetBoolSettingValue("Main", "HideMainWindowWhenRunning", false)) || - (QtHost::CanRenderToMainWindow() && - (isRenderingFullscreen() || s_system_locked.load(std::memory_order_relaxed))) || - QtHost::InNoGUIMode()); + return (!isRenderingToMain() && wantsDisplayWidget() && + ((Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && + Host::GetBoolSettingValue("Main", "HideMainWindowWhenRunning", false)) || + (QtHost::CanRenderToMainWindow() && + (isRenderingFullscreen() || s_system_locked.load(std::memory_order_relaxed))))) || + QtHost::InNoGUIMode(); } void MainWindow::switchToGameListView() @@ -2434,7 +2429,7 @@ void MainWindow::saveStateToConfig() if (!isVisible() || ((windowState() & Qt::WindowFullScreen) != Qt::WindowNoState)) return; - bool changed = QtUtils::SaveWindowGeometry("MainWindow", this, false); + bool changed = false; const QByteArray state(saveState()); const QByteArray state_b64(state.toBase64()); @@ -2445,14 +2440,14 @@ void MainWindow::saveStateToConfig() changed = true; } + changed |= QtUtils::SaveWindowGeometry("MainWindow", this, false); + if (changed) Host::CommitBaseSettingChanges(); } void MainWindow::restoreStateFromConfig() { - QtUtils::RestoreWindowGeometry("MainWindow", this); - { const std::string state_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowState"); const QByteArray state = QByteArray::fromBase64(QByteArray::fromStdString(state_b64)); @@ -2473,6 +2468,8 @@ void MainWindow::restoreStateFromConfig() m_ui.actionViewStatusBar->setChecked(!m_ui.statusBar->isHidden()); } } + + QtUtils::RestoreWindowGeometry("MainWindow", this); } void MainWindow::saveDisplayWindowGeometryToConfig() @@ -2484,13 +2481,29 @@ void MainWindow::saveDisplayWindowGeometryToConfig() return; } - QtUtils::SaveWindowGeometry("DisplayWindow", container); + const char* key = m_display_widget->windowPositionKey(); + if (key) + QtUtils::SaveWindowGeometry(key, container); } void MainWindow::restoreDisplayWindowGeometryFromConfig() { QWidget* const container = getDisplayContainer(); - if (!QtUtils::RestoreWindowGeometry("DisplayWindow", container)) + DebugAssert(m_display_widget); + + // just sync it with the main window if we're not using nogui modem, config will be stale + if (QtHost::CanRenderToMainWindow()) + { + container->setGeometry(geometry()); + return; + } + + // we don't want the temporary windowed window to be positioned on a different monitor, so use the main window + // coordinates... unless you're on wayland, too fucking bad, broken by design. + const bool use_main_window_pos = QtHost::UseMainWindowGeometryForDisplayWindow(); + m_display_widget->setWindowPositionKey(use_main_window_pos ? "MainWindow" : "DisplayWindow"); + + if (!QtUtils::RestoreWindowGeometry(m_display_widget->windowPositionKey(), container)) { // default size container->resize(640, 480); @@ -2641,7 +2654,7 @@ void MainWindow::changeEvent(QEvent* event) static_cast(event)->oldState() & Qt::WindowMinimized) { // TODO: This should check the render-to-main option. - if (m_display_widget) + if (isRenderingToMain()) g_emu_thread->redrawDisplayWindow(); } @@ -2785,6 +2798,10 @@ bool MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, b lock.cancelResume(); } + // If we're running in batch mode, don't show the main window after shutting down. + if (QtHost::InBatchMode()) + m_is_closing = true; + // Now we can actually shut down the VM. g_emu_thread->shutdownSystem(save_state, check_memcard_busy); return true; @@ -2824,6 +2841,13 @@ void MainWindow::checkForSettingChanges() } } + // don't change state if temporary unfullscreened + if (m_display_widget && !QtHost::IsSystemLocked() && !isRenderingFullscreen()) + { + if (QtHost::CanRenderToMainWindow() != isRenderingToMain()) + g_emu_thread->updateDisplayWindow(); + } + LogWindow::updateSettings(); updateWindowState(); } @@ -2986,7 +3010,7 @@ void MainWindow::onToolsCoverDownloaderTriggered() // This can be invoked via big picture, so exit fullscreen. if (isRenderingFullscreen()) { - g_emu_thread->setFullscreen(false, true); + g_emu_thread->setFullscreen(false); // wait for the fullscreen request to actually go through, otherwise the downloader appears behind the main window. QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, [this]() { return isRenderingFullscreen(); }); @@ -3181,14 +3205,10 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() // However, we do not want to switch back to render-to-main, the window might have generated this event. if (was_fullscreen) { - g_emu_thread->setFullscreen(false, false); + g_emu_thread->setFullscreen(false); // Container could change... thanks Wayland. - QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, [this]() { - QWidget* container; - return (s_system_valid && - (g_emu_thread->isFullscreen() || !(container = getDisplayContainer()) || container->isFullScreen())); - }); + QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, [this]() { return isRenderingFullscreen(); }); } if (!was_paused) @@ -3228,7 +3248,7 @@ MainWindow::SystemLock::~SystemLock() DebugAssert(s_system_locked.load(std::memory_order_relaxed) > 0); s_system_locked.fetch_sub(1, std::memory_order_release); if (m_was_fullscreen) - g_emu_thread->setFullscreen(true, true); + g_emu_thread->setFullscreen(true); if (!m_was_paused) g_emu_thread->setSystemPaused(false); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index dea06a4cc..e4cbfc268 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -138,8 +138,7 @@ private Q_SLOTS: void onStatusMessage(const QString& message); std::optional acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, - bool render_to_main, bool surfaceless, bool use_main_window_pos, - Error* error); + bool surfaceless, Error* error); void displayResizeRequested(qint32 width, qint32 height); void releaseRenderWindow(); void focusDisplayWidget(); @@ -250,7 +249,7 @@ private: void updateShortcutActions(bool starting); void updateStatusBarWidgetVisibility(); void updateWindowTitle(); - void updateWindowState(bool force_visible = false); + void updateWindowState(); void setProgressBar(int current, int total); void clearProgressBar(); @@ -269,7 +268,7 @@ private: void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); bool wantsDisplayWidget() const; - void createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos); + void createDisplayWidget(bool fullscreen, bool render_to_main); void destroyDisplayWidget(bool show_game_list); void updateDisplayWidgetCursor(); void updateDisplayRelatedActions(bool has_surface, bool fullscreen); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 7308b8d9a..dd21f5397 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -568,27 +568,11 @@ void Host::LoadSettings(const SettingsInterface& si, std::unique_lockcheckForSettingsChanges(old_settings); + // NOTE: emu thread, push to UI thread + if (g_main_window) + QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection); } void EmuThread::setDefaultSettings(bool system /* = true */, bool controller /* = true */) @@ -649,6 +633,12 @@ bool QtHost::CanRenderToMainWindow() return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) && !InNoGUIMode(); } +bool QtHost::UseMainWindowGeometryForDisplayWindow() +{ + // nogui _or_ main window mode, since we want to use it for temporary unfullscreens + return !Host::GetBoolSettingValue("Main", "RenderToSeparateWindow", false) || InNoGUIMode(); +} + void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height) { if (g_emu_thread->isFullscreen()) @@ -729,7 +719,6 @@ void EmuThread::startFullscreenUI() // we want settings loaded so we choose the correct renderer // this also sorts out input sources. System::LoadSettings(false); - m_is_rendering_to_main = QtHost::CanRenderToMainWindow(); // borrow the game start fullscreen flag const bool start_fullscreen = @@ -799,8 +788,6 @@ void EmuThread::bootSystem(std::shared_ptr params) if (System::IsValidOrInitializing()) return; - m_is_rendering_to_main = QtHost::CanRenderToMainWindow(); - Error error; if (!System::BootSystem(std::move(*params), &error)) { @@ -913,15 +900,14 @@ void EmuThread::toggleFullscreen() return; } - setFullscreen(!m_is_fullscreen, true); + setFullscreen(!m_is_fullscreen); } -void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main) +void EmuThread::setFullscreen(bool fullscreen) { if (!isCurrentThread()) { - QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen), - Q_ARG(bool, allow_render_to_main)); + QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen)); return; } @@ -929,7 +915,6 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main) return; m_is_fullscreen = fullscreen; - m_is_rendering_to_main = allow_render_to_main && QtHost::CanRenderToMainWindow(); GPUThread::UpdateDisplayWindow(fullscreen); } @@ -944,7 +929,7 @@ void Host::SetFullscreen(bool enabled) if (QtHost::IsSystemLocked()) return; - g_emu_thread->setFullscreen(enabled, true); + g_emu_thread->setFullscreen(enabled); } void EmuThread::setSurfaceless(bool surfaceless) @@ -962,6 +947,17 @@ void EmuThread::setSurfaceless(bool surfaceless) GPUThread::UpdateDisplayWindow(false); } +void EmuThread::updateDisplayWindow() +{ + if (!isCurrentThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::updateDisplayWindow, Qt::QueuedConnection); + return; + } + + GPUThread::UpdateDisplayWindow(m_is_fullscreen); +} + void EmuThread::requestDisplaySize(float scale) { if (!isCurrentThread()) @@ -983,12 +979,8 @@ std::optional EmuThread::acquireRenderWindow(RenderAPI render_api, b m_is_fullscreen = fullscreen; - const bool window_fullscreen = m_is_fullscreen && !exclusive_fullscreen; - const bool render_to_main = !fullscreen && m_is_rendering_to_main; - const bool use_main_window_pos = QtHost::CanRenderToMainWindow(); - - return emit onAcquireRenderWindowRequested(render_api, window_fullscreen, exclusive_fullscreen, render_to_main, - m_is_surfaceless, use_main_window_pos, error); + return emit onAcquireRenderWindowRequested(render_api, m_is_fullscreen, exclusive_fullscreen, m_is_surfaceless, + error); } void EmuThread::releaseRenderWindow() diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 301dfcf91..1868fec7a 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include class QActionGroup; class QEventLoop; @@ -97,7 +97,6 @@ public: ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; } ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; } - ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; } ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE InputDeviceListModel* getInputDeviceListModel() const { return m_input_device_list_model.get(); } @@ -111,8 +110,6 @@ public: void stopBackgroundControllerPollTimer(); void wakeThread(); - void checkForSettingsChanges(const Settings& old_settings); - void bootOrLoadState(std::string path); void updatePerformanceCounters(const GPUBackend* gpu_backend); @@ -146,8 +143,7 @@ Q_SIGNALS: void gameListRefreshed(); void gameListRowsChanged(const QList& rows_changed); std::optional onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen, - bool exclusive_fullscreen, bool render_to_main, - bool surfaceless, bool use_main_window_pos, Error* error); + bool exclusive_fullscreen, bool surfaceless, Error* error); void onResizeRenderWindowRequested(qint32 width, qint32 height); void onReleaseRenderWindowRequested(); void focusDisplayWidgetRequested(); @@ -210,8 +206,9 @@ public Q_SLOTS: void saveScreenshot(); void redrawDisplayWindow(); void toggleFullscreen(); - void setFullscreen(bool fullscreen, bool allow_render_to_main); + void setFullscreen(bool fullscreen); void setSurfaceless(bool surfaceless); + void updateDisplayWindow(); void requestDisplaySize(float scale); void applyCheat(const QString& name); void reloadPostProcessingShaders(); @@ -253,7 +250,6 @@ private: std::unique_ptr m_input_device_list_model; bool m_shutdown_flag = false; - bool m_is_rendering_to_main = false; bool m_is_fullscreen = false; bool m_is_fullscreen_ui_started = false; bool m_gpu_thread_run_idle = false; @@ -364,6 +360,9 @@ bool IsRunningOnWayland(); /// Returns true if rendering to the main window should be allowed. bool CanRenderToMainWindow(); +/// Returns true if the separate-window display widget should use the main window coordinates. +bool UseMainWindowGeometryForDisplayWindow(); + /// Default language for the platform. const char* GetDefaultLanguage();