diff --git a/src/core/gpu_thread.cpp b/src/core/gpu_thread.cpp index 1211c029c..6dfc8ab4a 100644 --- a/src/core/gpu_thread.cpp +++ b/src/core/gpu_thread.cpp @@ -696,16 +696,13 @@ void GPUThread::DestroyDeviceOnThread(bool clear_fsui_state) // Presenter should be gone by this point Assert(!s_state.gpu_presenter); - const bool has_window = g_gpu_device->HasMainSwapChain(); - FullscreenUI::Shutdown(clear_fsui_state); ImGuiManager::Shutdown(); INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI())); g_gpu_device->Destroy(); g_gpu_device.reset(); - if (has_window) - Host::ReleaseRenderWindow(); + Host::ReleaseRenderWindow(); UpdateRunIdle(); s_state.render_window_info = WindowInfo(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 881c72880..53bc349dc 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1746,6 +1746,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) { Error::SetStringFmt(error, "File '{}' is not a valid executable to boot.", Path::GetFileName(parameters.override_exe)); + Host::OnSystemStopping(); DestroySystem(); return false; } @@ -1786,6 +1787,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) if (cancelled) { // Technically a failure, but user-initiated. Returning false here would try to display a non-existent error. + Host::OnSystemStopping(); DestroySystem(); return true; } @@ -1805,6 +1807,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) parameters.override_fullscreen.value_or(ShouldStartFullscreen()), error) || !CheckForRequiredSubQ(error)) { + Host::OnSystemStopping(); DestroySystem(); return false; } @@ -1829,6 +1832,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) { Error::AddPrefixFmt(error, "Failed to load save state file '{}' for booting:\n", Path::GetFileName(parameters.save_state)); + Host::OnSystemStopping(); DestroySystem(); return false; } @@ -2020,6 +2024,7 @@ void System::AbnormalShutdown(const std::string_view reason) // Immediately switch to destroying and exit execution to get out of here. s_state.state = State::Stopping; std::atomic_thread_fence(std::memory_order_release); + Host::OnSystemStopping(); if (s_state.system_executing) InterruptExecution(); else @@ -5256,6 +5261,7 @@ void System::ShutdownSystem(bool save_resume_state) } } + Host::OnSystemStopping(); s_state.state = State::Stopping; std::atomic_thread_fence(std::memory_order_release); if (!s_state.system_executing) diff --git a/src/core/system_private.h b/src/core/system_private.h index 730720386..031fd18f0 100644 --- a/src/core/system_private.h +++ b/src/core/system_private.h @@ -99,6 +99,9 @@ void OnSystemStarting(); /// Called when the VM is created. void OnSystemStarted(); +/// Called when the VM is shutting down. +void OnSystemStopping(); + /// Called when the VM is shut down or destroyed. void OnSystemDestroyed(); diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp index d9cc29ba7..cfe4c77e8 100644 --- a/src/duckstation-mini/mini_host.cpp +++ b/src/duckstation-mini/mini_host.cpp @@ -1069,6 +1069,10 @@ void Host::OnSystemResumed() { } +void Host::OnSystemStopping() +{ +} + void Host::OnSystemDestroyed() { } diff --git a/src/duckstation-qt/displaywidget.cpp b/src/duckstation-qt/displaywidget.cpp index 035cf6fb6..b4d42e90d 100644 --- a/src/duckstation-qt/displaywidget.cpp +++ b/src/duckstation-qt/displaywidget.cpp @@ -142,6 +142,10 @@ void DisplayWidget::handleCloseEvent(QCloseEvent* event) QMetaObject::invokeMethod(g_main_window, "requestShutdown", Qt::QueuedConnection, Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, false), Q_ARG(bool, true)); } + else if (QtHost::IsFullscreenUIStarted()) + { + g_emu_thread->stopFullscreenUI(); + } else { QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 2d48b2296..45edda922 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -270,10 +270,11 @@ std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, // 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. + // Container can also be null if we're messing with settings while surfaceless. const bool has_container = (m_display_container != nullptr); const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main); - if (m_display_created && !is_rendering_to_main && !render_to_main && has_container == needs_container && - !needs_container && !changing_surfaceless) + if (container && !is_rendering_to_main && !render_to_main && has_container == needs_container && !needs_container && + !changing_surfaceless) { DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed")); m_exclusive_fullscreen_requested = exclusive_fullscreen; @@ -304,35 +305,39 @@ std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, } destroyDisplayWidget(surfaceless); - m_display_created = true; - // if we're going to surfaceless, we're done here - if (surfaceless) - return WindowInfo(); - - createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); - - std::optional wi = m_display_widget->getWindowInfo(render_api, error); - if (!wi.has_value()) + std::optional wi; + if (!surfaceless) { - QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); - destroyDisplayWidget(true); - return std::nullopt; - } + createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); - g_emu_thread->connectDisplaySignals(m_display_widget); + wi = m_display_widget->getWindowInfo(render_api, error); + if (!wi.has_value()) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); + destroyDisplayWidget(true); + return std::nullopt; + } + + g_emu_thread->connectDisplaySignals(m_display_widget); + } + else + { + wi = WindowInfo(); + } updateWindowTitle(); updateWindowState(); - updateDisplayWidgetCursor(); - updateDisplayRelatedActions(true, render_to_main, fullscreen); - QtUtils::ShowOrRaiseWindow(QtUtils::GetRootWidget(m_display_widget)); - m_display_widget->setFocus(); - return wi; } +bool MainWindow::wantsDisplayWidget() const +{ + // big picture or system created + return (s_system_starting || s_system_valid || s_fullscreen_ui_started); +} + void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos) { // If we're rendering to main and were hidden (e.g. coming back from fullscreen), @@ -393,8 +398,14 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool updateDisplayRelatedActions(true, render_to_main, fullscreen); updateShortcutActions(false); + updateDisplayWidgetCursor(); + // We need the surface visible. QGuiApplication::sync(); + + if (!render_to_main) + QtUtils::ShowOrRaiseWindow(QtUtils::GetRootWidget(m_display_widget)); + m_display_widget->setFocus(); } void MainWindow::displayResizeRequested(qint32 width, qint32 height) @@ -423,49 +434,46 @@ void MainWindow::releaseRenderWindow() { // Now we can safely destroy the display window. destroyDisplayWidget(true); - m_display_created = false; - m_exclusive_fullscreen_requested = false; - - updateDisplayRelatedActions(false, false, false); - updateShortcutActions(false); - - m_ui.actionViewSystemDisplay->setEnabled(false); - m_ui.actionFullscreen->setEnabled(false); } void MainWindow::destroyDisplayWidget(bool show_game_list) { - if (!m_display_widget) - return; - - if (!isRenderingFullscreen() && !isRenderingToMain()) - saveDisplayWindowGeometryToConfig(); - - if (m_display_container) - m_display_container->removeDisplayWidget(); - - if (isRenderingToMain()) + if (m_display_widget) { - AssertMsg(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack"); - m_ui.mainContainer->removeWidget(m_display_widget); - if (show_game_list) + if (!isRenderingFullscreen() && !isRenderingToMain()) + saveDisplayWindowGeometryToConfig(); + + if (m_display_container) + m_display_container->removeDisplayWidget(); + + if (isRenderingToMain()) { - m_ui.mainContainer->setCurrentIndex(0); - m_game_list_widget->resizeListViewColumnsToFit(); + AssertMsg(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack"); + m_ui.mainContainer->removeWidget(m_display_widget); + if (show_game_list) + { + m_ui.mainContainer->setCurrentIndex(0); + m_game_list_widget->resizeListViewColumnsToFit(); + } + } + + if (m_display_widget) + { + m_display_widget->destroy(); + m_display_widget = nullptr; + } + + if (m_display_container) + { + m_display_container->deleteLater(); + m_display_container = nullptr; } } - if (m_display_widget) - { - m_display_widget->destroy(); - m_display_widget = nullptr; - } + m_exclusive_fullscreen_requested = false; - if (m_display_container) - { - m_display_container->deleteLater(); - m_display_container = nullptr; - } + updateDisplayRelatedActions(false, false, false); + updateShortcutActions(false); } void MainWindow::updateDisplayWidgetCursor() @@ -481,7 +489,7 @@ void MainWindow::updateDisplayWidgetCursor() void MainWindow::updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen) { // rendering to main, or switched to gamelist/grid - m_ui.actionViewSystemDisplay->setEnabled((has_surface && render_to_main) || (!has_surface && g_gpu_device)); + m_ui.actionViewSystemDisplay->setEnabled(wantsDisplayWidget() && QtHost::CanRenderToMainWindow()); m_ui.menuWindowSize->setEnabled(has_surface && !fullscreen); m_ui.actionFullscreen->setEnabled(has_surface); @@ -567,7 +575,7 @@ void MainWindow::onSystemResumed() } } -void MainWindow::onSystemDestroyed() +void MainWindow::onSystemStopping() { // update UI { @@ -580,6 +588,14 @@ void MainWindow::onSystemDestroyed() s_system_paused = false; s_undo_state_timestamp.reset(); + updateEmulationActions(false, false, s_achievements_hardcore_mode); + updateStatusBarWidgetVisibility(); +} + +void MainWindow::onSystemDestroyed() +{ + Assert(!s_system_starting && !s_system_valid); + // If we're closing or in batch mode, quit the whole application now. if (m_is_closing || QtHost::InBatchMode()) { @@ -588,8 +604,6 @@ void MainWindow::onSystemDestroyed() return; } - updateEmulationActions(false, false, s_achievements_hardcore_mode); - updateStatusBarWidgetVisibility(); if (m_display_widget) updateDisplayWidgetCursor(); else @@ -718,7 +732,7 @@ void MainWindow::quit() } // Big picture might still be active. - if (m_display_created) + if (s_fullscreen_ui_started) g_emu_thread->stopFullscreenUI(); // Ensure subwindows are removed before quitting. That way the log window cancelling @@ -748,7 +762,7 @@ void MainWindow::recreate() // Remove subwindows before switching to surfaceless, because otherwise e.g. the debugger can cause funkyness. destroySubWindows(); - const bool was_display_created = m_display_created; + const bool was_display_created = wantsDisplayWidget(); const bool was_fullscreen = (was_display_created && g_emu_thread->isFullscreen()); if (was_display_created) { @@ -759,7 +773,6 @@ void MainWindow::recreate() g_emu_thread->setSurfaceless(true); QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, [this]() { return (m_display_widget || !g_emu_thread->isSurfaceless()); }); - m_display_created = false; } // We need to close input sources, because e.g. DInput uses our window handle. @@ -811,6 +824,11 @@ void MainWindow::recreate() notifyRAIntegrationOfWindowChange(); } +void MainWindow::ensureVisible() +{ + updateWindowState(true); +} + void MainWindow::destroySubWindows() { QtUtils::CloseAndDeleteWindow(m_memory_scanner_window); @@ -1375,8 +1393,7 @@ void MainWindow::onViewGameGridActionTriggered() void MainWindow::onViewSystemDisplayTriggered() { - if (m_display_created) - switchToEmulationView(); + switchToEmulationView(); } void MainWindow::onViewGameGridZoomInActionTriggered() @@ -2033,17 +2050,16 @@ void MainWindow::updateWindowState(bool force_visible) if (m_is_closing) return; - const bool hide_window = !isRenderingToMain() && shouldHideMainWindow(); + const bool hide_window = shouldHideMainWindow(); const bool disable_resize = Host::GetBoolSettingValue("Main", "DisableWindowResize", false); - const bool has_window = s_system_valid || m_display_widget; // Need to test both valid and display widget because of startup (vm invalid while window is created). - const bool visible = force_visible || !hide_window || !has_window; + 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 || !has_window; + const bool resizeable = force_visible || !disable_resize; if (visible) QtUtils::SetWindowResizeable(this, resizeable); @@ -2100,15 +2116,19 @@ bool MainWindow::shouldHideMouseCursor() const bool MainWindow::shouldHideMainWindow() const { - return Host::GetBoolSettingValue("Main", "HideMainWindowWhenRunning", false) || QtHost::CanRenderToMainWindow() || - QtHost::InNoGUIMode(); + // CanRenderToMain check is for temporary unfullscreens. + return !isRenderingToMain() && wantsDisplayWidget() && + (Host::GetBoolSettingValue("Main", "HideMainWindowWhenRunning", false) || + (QtHost::CanRenderToMainWindow() && + (isRenderingFullscreen() || s_system_locked.load(std::memory_order_relaxed))) || + QtHost::InNoGUIMode()); } void MainWindow::switchToGameListView() { if (!isShowingGameList()) { - if (m_display_created) + if (wantsDisplayWidget()) { m_was_paused_on_surface_loss = s_system_paused; if (!s_system_paused) @@ -2128,7 +2148,7 @@ void MainWindow::switchToGameListView() void MainWindow::switchToEmulationView() { - if (!m_display_created || !isShowingGameList()) + if (!wantsDisplayWidget() || !isShowingGameList()) return; // we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out. @@ -2141,7 +2161,11 @@ void MainWindow::switchToEmulationView() updateShortcutActions(false); if (m_display_widget) + { + if (!isRenderingToMain()) + QtUtils::ShowOrRaiseWindow(QtUtils::GetRootWidget(m_display_widget)); m_display_widget->setFocus(); + } } void MainWindow::connectSignals() @@ -2259,6 +2283,7 @@ void MainWindow::connectSignals() connect(g_emu_thread, &EmuThread::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget); connect(g_emu_thread, &EmuThread::systemStarting, this, &MainWindow::onSystemStarting); connect(g_emu_thread, &EmuThread::systemStarted, this, &MainWindow::onSystemStarted); + connect(g_emu_thread, &EmuThread::systemStopping, this, &MainWindow::onSystemStopping); connect(g_emu_thread, &EmuThread::systemDestroyed, this, &MainWindow::onSystemDestroyed); connect(g_emu_thread, &EmuThread::systemPaused, this, &MainWindow::onSystemPaused); connect(g_emu_thread, &EmuThread::systemResumed, this, &MainWindow::onSystemResumed); @@ -2596,10 +2621,10 @@ void MainWindow::showEvent(QShowEvent* event) void MainWindow::closeEvent(QCloseEvent* event) { // If there's no VM, we can just exit as normal. - if (!s_system_valid || !m_display_created) + if (!s_system_valid) { saveStateToConfig(); - if (m_display_created) + if (s_fullscreen_ui_started) g_emu_thread->stopFullscreenUI(); destroySubWindows(); QMainWindow::closeEvent(event); @@ -3145,6 +3170,9 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() #endif const bool was_paused = !s_system_valid || s_system_paused; + // Have to do this early to avoid making the main window visible. + s_system_locked.fetch_add(1, std::memory_order_release); + // We need to switch out of exclusive fullscreen before we can display our popup. // However, we do not want to switch back to render-to-main, the window might have generated this event. if (was_fullscreen) @@ -3171,7 +3199,10 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() } // Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen). - QWidget* dialog_parent = (s_system_valid && was_fullscreen) ? getDisplayContainer() : this; + QWidget* dialog_parent = getDisplayContainer(); + if (dialog_parent->parent()) + dialog_parent = this; + return SystemLock(dialog_parent, was_paused, was_fullscreen); } @@ -3179,13 +3210,11 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() MainWindow::SystemLock::SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen) : m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen) { - s_system_locked.fetch_add(1, std::memory_order_release); } MainWindow::SystemLock::SystemLock(SystemLock&& lock) : m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen) { - s_system_locked.fetch_add(1, std::memory_order_release); lock.m_dialog_parent = nullptr; lock.m_was_paused = true; lock.m_was_fullscreen = false; diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index c60ecc7a6..3d27da81d 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -125,6 +125,7 @@ public Q_SLOTS: void checkForUpdates(bool display_message); void recreate(); + void ensureVisible(); void* getNativeWindowId(); @@ -144,6 +145,7 @@ private Q_SLOTS: void onSettingsResetToDefault(bool system, bool controller); void onSystemStarting(); void onSystemStarted(); + void onSystemStopping(); void onSystemDestroyed(); void onSystemPaused(); void onSystemResumed(); @@ -264,6 +266,7 @@ private: void restoreStateFromConfig(); void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); + bool wantsDisplayWidget() const; void createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos); void destroyDisplayWidget(bool show_game_list); void updateDisplayWidgetCursor(); @@ -340,7 +343,6 @@ private: bool m_relative_mouse_mode = false; bool m_hide_mouse_cursor = false; - bool m_display_created = false; bool m_exclusive_fullscreen_requested = false; bool m_save_states_invalidated = false; bool m_was_paused_on_surface_loss = false; diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 3760d0be7..186c16b22 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -703,15 +703,17 @@ void EmuThread::startFullscreenUI() const bool start_fullscreen = (s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false)); + m_is_fullscreen_ui_started = true; + emit fullscreenUIStartedOrStopped(true); + Error error; if (!GPUThread::StartFullscreenUI(start_fullscreen, &error)) { Host::ReportErrorAsync("Error", error.GetDescription()); + m_is_fullscreen_ui_started = false; + emit fullscreenUIStartedOrStopped(false); return; } - - m_is_fullscreen_ui_started = true; - emit fullscreenUIStartedOrStopped(true); } void EmuThread::stopFullscreenUI() @@ -722,7 +724,7 @@ void EmuThread::stopFullscreenUI() // wait until the host display is gone QtUtils::ProcessEventsWithSleep(QEventLoop::ExcludeUserInputEvents, - []() { return (!QtHost::IsSystemValid() && g_gpu_device); }); + []() { return QtHost::IsFullscreenUIStarted(); }); return; } @@ -749,14 +751,9 @@ void EmuThread::exitFullscreenUI() const bool was_in_nogui_mode = std::exchange(s_nogui_mode, false); - // force a return to main window before exiting, otherwise qt will terminate the application - if (!m_is_rendering_to_main) - { - m_is_fullscreen = false; - m_is_rendering_to_main = true; - GPUThread::UpdateDisplayWindow(false); - } - + // force the main window to be visible, otherwise qt will terminate the application + QMetaObject::invokeMethod(g_main_window, &MainWindow::ensureVisible, Qt::QueuedConnection); + // then stop as normal stopFullscreenUI(); @@ -974,6 +971,7 @@ void EmuThread::releaseRenderWindow() { emit onReleaseRenderWindowRequested(); m_is_fullscreen = false; + m_is_surfaceless = false; } void EmuThread::connectDisplaySignals(DisplayWidget* widget) @@ -1019,6 +1017,11 @@ void Host::OnSystemResumed() g_emu_thread->stopBackgroundControllerPollTimer(); } +void Host::OnSystemStopping() +{ + emit g_emu_thread->systemStopping(); +} + void Host::OnSystemDestroyed() { g_emu_thread->resetPerformanceCounters(); @@ -2757,7 +2760,6 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app, { INFO_LOG("Command Line: Using NoGUI mode."); s_nogui_mode = true; - s_batch_mode = true; continue; } else if (CHECK_ARG("-bios")) @@ -2907,6 +2909,9 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app, if (autoboot && autoboot->path.empty() && autoboot->save_state.empty() && !starting_bios) autoboot.reset(); + // nogui implies batch mode if autobooting and not running big picture mode + s_batch_mode = (s_batch_mode || (autoboot && !s_start_fullscreen_ui)); + // if we don't have autoboot, we definitely don't want batch mode (because that'll skip // scanning the game list). if (s_batch_mode && !autoboot && !s_start_fullscreen_ui) diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index f077ecac4..301dfcf91 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -137,6 +137,7 @@ Q_SIGNALS: void settingsResetToDefault(bool system, bool controller); void systemStarting(); void systemStarted(); + void systemStopping(); void systemDestroyed(); void systemPaused(); void systemResumed(); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index e5926090b..98ecabe94 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -286,6 +286,11 @@ void Host::OnSystemStarted() // } +void Host::OnSystemStopping() +{ + // +} + void Host::OnSystemDestroyed() { // diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index c7710fbea..fb359bcd0 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -321,7 +321,7 @@ void ImGuiManager::UpdateScale() const float window_scale = (g_gpu_device && g_gpu_device->HasMainSwapChain()) ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f; const float scale = std::max(window_scale * s_state.global_prescale, 1.0f); - const bool scale_changed = (scale == s_state.global_scale); + const bool scale_changed = (scale != s_state.global_scale); if (!ImGuiFullscreen::UpdateLayoutScale() && !scale_changed) return;