From a5755d12f1215a7e6d996d3772c5bf065d7f44a4 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 28 Nov 2025 23:30:50 +1000 Subject: [PATCH] Qt: Get rid of multiple sources of truth for fullscreen Only has UI and CPU thread "views" now. --- src/core/fullscreenui.cpp | 6 +-- src/core/gpu_presenter.cpp | 2 +- src/core/gpu_thread.cpp | 66 ++++++++++++++++++------ src/core/gpu_thread.h | 16 +++++- src/core/gpu_thread_commands.h | 2 +- src/core/host.h | 14 ----- src/core/hotkeys.cpp | 2 +- src/duckstation-mini/mini_host.cpp | 40 ++------------ src/duckstation-qt/mainwindow.cpp | 2 +- src/duckstation-qt/qthost.cpp | 47 ++++++++--------- src/duckstation-qt/qthost.h | 6 +-- src/duckstation-regtest/regtest_host.cpp | 15 ++---- 12 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/core/fullscreenui.cpp b/src/core/fullscreenui.cpp index 149020dce..4d08828cd 100644 --- a/src/core/fullscreenui.cpp +++ b/src/core/fullscreenui.cpp @@ -953,7 +953,7 @@ void FullscreenUI::DoDesktopMode() void FullscreenUI::DoToggleFullscreen() { - Host::RunOnCPUThread([]() { Host::SetFullscreen(!Host::IsFullscreen()); }); + Host::RunOnCPUThread([]() { GPUThread::SetFullscreen(!GPUThread::IsFullscreen()); }); } ////////////////////////////////////////////////////////////////////////// @@ -2208,9 +2208,7 @@ void FullscreenUI::DrawResumeStateSelector() void FullscreenUI::ExitFullscreenAndOpenURL(std::string_view url) { Host::RunOnCPUThread([url = std::string(url)]() { - if (Host::IsFullscreen()) - Host::SetFullscreen(false); - + GPUThread::SetFullscreen(false); Host::OpenURL(url); }); } diff --git a/src/core/gpu_presenter.cpp b/src/core/gpu_presenter.cpp index 58ae6ad04..a72570d60 100644 --- a/src/core/gpu_presenter.cpp +++ b/src/core/gpu_presenter.cpp @@ -1325,7 +1325,7 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo if (pres == GPUDevice::PresentResult::ExclusiveFullscreenLost) [[unlikely]] { WARNING_LOG("Lost exclusive fullscreen."); - Host::SetFullscreen(false); + GPUThread::SetFullscreen(false); } if (!skip_present) diff --git a/src/core/gpu_thread.cpp b/src/core/gpu_thread.cpp index 9e37c6442..16a65727b 100644 --- a/src/core/gpu_thread.cpp +++ b/src/core/gpu_thread.cpp @@ -101,6 +101,7 @@ struct ALIGN_TO_CACHE_LINE State WindowInfo render_window_info; std::optional requested_renderer; // TODO: Non thread safe accessof this bool use_gpu_thread = false; + bool requested_fullscreen = false; // Hot variables between both threads. ALIGN_TO_CACHE_LINE std::atomic command_fifo_write_ptr{0}; @@ -538,10 +539,13 @@ bool GPUThread::Reconfigure(std::optional renderer, bool upload_vra { INFO_LOG("Reconfiguring GPU thread."); + s_state.requested_renderer = renderer; + s_state.requested_fullscreen = fullscreen.value_or(s_state.requested_fullscreen); + bool result = false; GPUThreadReconfigureCommand* cmd = AllocateCommand(GPUBackendCommandType::Reconfigure); - cmd->renderer = renderer; - cmd->fullscreen = fullscreen; + cmd->renderer = s_state.requested_renderer; + cmd->fullscreen = s_state.requested_fullscreen; cmd->start_fullscreen_ui = start_fullscreen_ui; cmd->vsync_mode = System::GetEffectiveVSyncMode(); cmd->allow_present_throttle = System::ShouldAllowPresentThrottle(); @@ -596,7 +600,6 @@ std::optional GPUThread::GetRequestedRenderer() bool GPUThread::CreateGPUBackend(GPURenderer renderer, bool upload_vram, bool fullscreen, bool force_recreate_device, Error* error) { - s_state.requested_renderer = renderer; return Reconfigure(renderer, upload_vram, fullscreen ? std::optional(true) : std::nullopt, std::nullopt, force_recreate_device, error); } @@ -618,6 +621,11 @@ bool GPUThread::IsGPUBackendRequested() return s_state.requested_renderer.has_value(); } +bool GPUThread::IsFullscreen() +{ + return s_state.requested_fullscreen; +} + bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_fsui_state_on_failure, Error* error) { DebugAssert(!g_gpu_device); @@ -723,7 +731,10 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_ // Switch to borderless if exclusive failed. if (fullscreen_mode.has_value() && !CheckExclusiveFullscreenOnThread()) + { + WARNING_LOG("Failed to get exclusive fullscreen, requesting borderless fullscreen instead."); UpdateDisplayWindowOnThread(true, false); + } return true; } @@ -854,12 +865,11 @@ void GPUThread::ReconfigureOnThread(GPUThreadReconfigureCommand* cmd) if (cmd->force_recreate_device || !GPUDevice::IsSameRenderAPI(current_api, expected_api)) { Timer timer; - const bool fullscreen = cmd->fullscreen.value_or(Host::IsFullscreen()); DestroyGPUPresenterOnThread(); DestroyDeviceOnThread(false); Error local_error; - if (!CreateDeviceOnThread(expected_api, fullscreen, false, &local_error)) + if (!CreateDeviceOnThread(expected_api, cmd->fullscreen, false, &local_error)) { Host::AddIconOSDMessage( OSDMessageType::Error, "DeviceSwitchFailed", ICON_FA_PAINT_ROLLER, @@ -868,7 +878,7 @@ void GPUThread::ReconfigureOnThread(GPUThreadReconfigureCommand* cmd) local_error.GetDescription())); Host::ReleaseRenderWindow(); - if (current_api == RenderAPI::None || !CreateDeviceOnThread(current_api, fullscreen, true, &local_error)) + if (current_api == RenderAPI::None || !CreateDeviceOnThread(current_api, cmd->fullscreen, true, &local_error)) { if (cmd->error_ptr) *cmd->error_ptr = local_error; @@ -909,11 +919,8 @@ void GPUThread::ReconfigureOnThread(GPUThreadReconfigureCommand* cmd) } else if (s_state.requested_fullscreen_ui) { - if (!(*cmd->out_result = - g_gpu_device || CreateDeviceOnThread(expected_api, cmd->fullscreen.value_or(false), true, cmd->error_ptr))) - { + if (!(*cmd->out_result = g_gpu_device || CreateDeviceOnThread(expected_api, cmd->fullscreen, true, cmd->error_ptr))) return; - } // Don't need to present game frames anymore. DestroyGPUPresenterOnThread(); @@ -992,7 +999,7 @@ void GPUThread::SetThreadEnabled(bool enabled) return; } - const bool fullscreen = Host::IsFullscreen(); + const bool requested_fullscreen = s_state.requested_fullscreen; const bool requested_fullscreen_ui = s_state.requested_fullscreen_ui; const std::optional requested_renderer = s_state.requested_renderer; @@ -1017,8 +1024,8 @@ void GPUThread::SetThreadEnabled(bool enabled) s_state.use_gpu_thread = enabled; Error error; - if (!Reconfigure(requested_renderer, requested_renderer.has_value(), fullscreen, requested_fullscreen_ui, true, - &error)) + if (!Reconfigure(requested_renderer, requested_renderer.has_value(), requested_fullscreen, requested_fullscreen_ui, + true, &error)) { ERROR_LOG("Reconfigure failed: {}", error.GetDescription()); ReportFatalErrorAndShutdown(fmt::format("Reconfigure failed: {}", error.GetDescription())); @@ -1268,19 +1275,48 @@ void GPUThread::ResizeDisplayWindowOnThread(u32 width, u32 height, float scale) Error error; if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error)) { + // ick, CPU thread read, but this is unlikely to happen in the first place ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription()); - UpdateDisplayWindowOnThread(Host::IsFullscreen(), true); + UpdateDisplayWindowOnThread(s_state.requested_fullscreen, true); return; } DisplayWindowResizedOnThread(); } -void GPUThread::UpdateDisplayWindow(bool fullscreen) +void GPUThread::UpdateDisplayWindow() { + RunOnThread([fullscreen = s_state.requested_fullscreen]() { UpdateDisplayWindowOnThread(fullscreen, true); }); +} + +void GPUThread::SetFullscreen(bool fullscreen) +{ + // Technically not safe to read g_gpu_device here on the CPU thread, but we do sync on create/destroy. + if (s_state.requested_fullscreen == fullscreen || !Host::CanChangeFullscreenMode(fullscreen) || !g_gpu_device) + return; + + s_state.requested_fullscreen = fullscreen; RunOnThread([fullscreen]() { UpdateDisplayWindowOnThread(fullscreen, true); }); } +void GPUThread::SetFullscreenWithCompletionHandler(bool fullscreen, AsyncCallType completion_handler) +{ + if (s_state.requested_fullscreen == fullscreen || !Host::CanChangeFullscreenMode(fullscreen) || !g_gpu_device) + { + if (completion_handler) + completion_handler(); + + return; + } + + s_state.requested_fullscreen = fullscreen; + RunOnThread([fullscreen, completion_handler = std::move(completion_handler)]() { + UpdateDisplayWindowOnThread(fullscreen, true); + if (completion_handler) + completion_handler(); + }); +} + void GPUThread::UpdateDisplayWindowOnThread(bool fullscreen, bool allow_exclusive_fullscreen) { // In case we get the event late. diff --git a/src/core/gpu_thread.h b/src/core/gpu_thread.h index ecb71dfdc..c0ba4a94d 100644 --- a/src/core/gpu_thread.h +++ b/src/core/gpu_thread.h @@ -52,13 +52,16 @@ bool CreateGPUBackend(GPURenderer renderer, bool upload_vram, bool fullscreen, b void DestroyGPUBackend(); bool HasGPUBackend(); bool IsGPUBackendRequested(); +bool IsFullscreen(); /// Re-presents the current frame. Call when things like window resizes happen to re-display /// the current frame with the correct proportions. Should only be called from the CPU thread. void PresentCurrentFrame(); /// Handles fullscreen transitions and such. -void UpdateDisplayWindow(bool fullscreen); +void UpdateDisplayWindow(); +void SetFullscreen(bool fullscreen); +void SetFullscreenWithCompletionHandler(bool fullscreen, AsyncCallType completion_handler); /// Called when the window is resized. void ResizeDisplayWindow(s32 width, s32 height, float scale); @@ -110,6 +113,17 @@ bool PresentFrameAndRestoreContext(); namespace Host { +/// Called when the core is creating a render device. +/// This could also be fullscreen transition. +std::optional AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, + Error* error); + +/// Called when the core is finished with a render window. +void ReleaseRenderWindow(); + +/// Called before a fullscreen transition occurs. +bool CanChangeFullscreenMode(bool new_fullscreen_state); + /// Called when the pause state changes, or fullscreen UI opens. void OnGPUThreadRunIdleChanged(bool is_active); diff --git a/src/core/gpu_thread_commands.h b/src/core/gpu_thread_commands.h index 84bdb5ffc..2ea536c7f 100644 --- a/src/core/gpu_thread_commands.h +++ b/src/core/gpu_thread_commands.h @@ -73,10 +73,10 @@ struct GPUThreadReconfigureCommand : public GPUThreadCommand Error* error_ptr; bool* out_result; std::optional renderer; - std::optional fullscreen; std::optional start_fullscreen_ui; GPUVSyncMode vsync_mode; bool allow_present_throttle; + bool fullscreen; bool force_recreate_device; bool upload_vram; GPUSettings settings; diff --git a/src/core/host.h b/src/core/host.h index 9a2e4be2e..6f25af7e9 100644 --- a/src/core/host.h +++ b/src/core/host.h @@ -84,20 +84,6 @@ void RunOnCPUThread(std::function function, bool block = false); /// Safely executes a function on the main/UI thread. void RunOnUIThread(std::function function, bool block = false); -/// Called when the core is creating a render device. -/// This could also be fullscreen transition. -std::optional AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, - Error* error); - -/// Called when the core is finished with a render window. -void ReleaseRenderWindow(); - -/// Returns true if the hosting application is currently fullscreen. -bool IsFullscreen(); - -/// Alters fullscreen state of hosting application. -void SetFullscreen(bool enabled); - namespace Internal { /// Based on the current configuration, determines what the data directory is. diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index e13ed7a79..41a876554 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -135,7 +135,7 @@ DEFINE_NON_ANDROID_HOTKEY("TogglePause", TRANSLATE_NOOP("Hotkeys", "Interface"), DEFINE_NON_ANDROID_HOTKEY("ToggleFullscreen", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Toggle Fullscreen"), [](s32 pressed) { if (!pressed) - Host::SetFullscreen(!Host::IsFullscreen()); + GPUThread::SetFullscreen(!GPUThread::IsFullscreen()); }) DEFINE_HOTKEY("FastForward", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Fast Forward (Hold)"), diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp index 771d51c32..122811c4d 100644 --- a/src/duckstation-mini/mini_host.cpp +++ b/src/duckstation-mini/mini_host.cpp @@ -111,7 +111,6 @@ struct SDLHostState SDL_Window* sdl_window = nullptr; float sdl_window_scale = 0.0f; WindowInfo::PreRotation force_prerotation = WindowInfo::PreRotation::Identity; - std::atomic_bool fullscreen{false}; Threading::Thread cpu_thread; Threading::Thread gpu_thread; @@ -624,11 +623,7 @@ std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool f if (s_state.sdl_window) { wi = TranslateSDLWindowInfo(s_state.sdl_window, error); - if (wi.has_value()) - { - s_state.fullscreen.store(fullscreen, std::memory_order_release); - } - else + if (!wi.has_value()) { SDL_DestroyWindow(s_state.sdl_window); s_state.sdl_window = nullptr; @@ -661,7 +656,7 @@ void Host::ReleaseRenderWindow() return; Host::RunOnUIThread([]() { - if (!s_state.fullscreen.load(std::memory_order_acquire)) + if (!(SDL_GetWindowFlags(s_state.sdl_window) & SDL_WINDOW_FULLSCREEN)) { int window_x = SDL_WINDOWPOS_UNDEFINED, window_y = SDL_WINDOWPOS_UNDEFINED; int window_width = DEFAULT_WINDOW_WIDTH, window_height = DEFAULT_WINDOW_HEIGHT; @@ -669,10 +664,6 @@ void Host::ReleaseRenderWindow() SDL_GetWindowSize(s_state.sdl_window, &window_width, &window_height); MiniHost::SavePlatformWindowGeometry(window_x, window_y, window_width, window_height); } - else - { - s_state.fullscreen.store(false, std::memory_order_release); - } SDL_DestroyWindow(s_state.sdl_window); s_state.sdl_window = nullptr; @@ -683,27 +674,9 @@ void Host::ReleaseRenderWindow() s_state.platform_window_updated.Wait(); } -bool Host::IsFullscreen() +bool Host::CanChangeFullscreenMode(bool new_fullscreen_state) { - using namespace MiniHost; - - return s_state.fullscreen.load(std::memory_order_acquire); -} - -void Host::SetFullscreen(bool enabled) -{ - using namespace MiniHost; - - if (!s_state.sdl_window || s_state.fullscreen.load(std::memory_order_acquire) == enabled) - return; - - if (!SDL_SetWindowFullscreen(s_state.sdl_window, enabled)) - { - ERROR_LOG("SDL_SetWindowFullscreen() failed: {}", SDL_GetError()); - return; - } - - s_state.fullscreen.store(enabled, std::memory_order_release); + return true; } void Host::BeginTextInput() @@ -747,9 +720,6 @@ bool MiniHost::GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* h void MiniHost::SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height) { - if (Host::IsFullscreen()) - return; - const auto lock = Host::GetSettingsLock(); s_state.base_settings_interface.SetIntValue("UI", "MainWindowX", x); s_state.base_settings_interface.SetIntValue("UI", "MainWindowY", y); @@ -1348,7 +1318,7 @@ void Host::RequestResizeHostDisplay(s32 width, s32 height) { using namespace MiniHost; - if (!s_state.sdl_window || s_state.fullscreen.load(std::memory_order_acquire)) + if (!s_state.sdl_window || SDL_GetWindowFlags(s_state.sdl_window) & SDL_WINDOW_FULLSCREEN) return; SDL_SetWindowSize(s_state.sdl_window, width, height); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 8d1aa4ae4..a0ab287a8 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -426,7 +426,7 @@ void MainWindow::exitFullscreen(bool wait_for_completion) void MainWindow::displayResizeRequested(qint32 width, qint32 height) { - if (!m_display_widget) + if (!m_display_widget || isRenderingFullscreen()) return; // unapply the pixel scaling factor for hidpi diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 770d3631b..ad505c724 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -653,9 +653,6 @@ bool QtHost::UseMainWindowGeometryForDisplayWindow() void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height) { - if (g_emu_thread->isFullscreen()) - return; - emit g_emu_thread->onResizeRenderWindowRequested(new_window_width, new_window_height); } @@ -725,7 +722,7 @@ void EmuThread::startFullscreenUI() return; } - if (System::IsValid() || m_is_fullscreen_ui_started) + if (System::IsValid() || GPUThread::IsFullscreenUIRequested()) return; // we want settings loaded so we choose the correct renderer @@ -922,7 +919,7 @@ void EmuThread::toggleFullscreen() return; } - setFullscreen(!m_is_fullscreen); + GPUThread::SetFullscreen(!GPUThread::IsFullscreen()); } void EmuThread::setFullscreen(bool fullscreen) @@ -933,25 +930,22 @@ void EmuThread::setFullscreen(bool fullscreen) return; } - if (!g_gpu_device || m_is_fullscreen == fullscreen) - return; - - m_is_fullscreen = fullscreen; - GPUThread::UpdateDisplayWindow(fullscreen); + GPUThread::SetFullscreen(fullscreen); } -bool Host::IsFullscreen() +void EmuThread::setFullscreenWithCompletionHandler(bool fullscreen, std::function completion_handler) { - return g_emu_thread->isFullscreen(); -} - -void Host::SetFullscreen(bool enabled) -{ - // don't mess with fullscreen while locked - if (QtHost::IsSystemLocked()) + if (!isCurrentThread()) + { + DebugAssert(QThread::isMainThread()); + QMetaObject::invokeMethod(this, &EmuThread::setFullscreenWithCompletionHandler, Qt::QueuedConnection, fullscreen, + [completion_handler = std::move(completion_handler)]() mutable { + Host::RunOnUIThread(std::move(completion_handler)); + }); return; + } - g_emu_thread->setFullscreen(enabled); + GPUThread::SetFullscreenWithCompletionHandler(fullscreen, std::move(completion_handler)); } void EmuThread::updateDisplayWindow() @@ -962,7 +956,7 @@ void EmuThread::updateDisplayWindow() return; } - GPUThread::UpdateDisplayWindow(m_is_fullscreen); + GPUThread::UpdateDisplayWindow(); } void EmuThread::requestDisplaySize(float scale) @@ -982,17 +976,12 @@ void EmuThread::requestDisplaySize(float scale) std::optional EmuThread::acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, Error* error) { - DebugAssert(g_gpu_device); - - m_is_fullscreen = fullscreen; - - return emit onAcquireRenderWindowRequested(render_api, m_is_fullscreen, exclusive_fullscreen, error); + return emit onAcquireRenderWindowRequested(render_api, fullscreen, exclusive_fullscreen, error); } void EmuThread::releaseRenderWindow() { emit onReleaseRenderWindowRequested(); - m_is_fullscreen = false; } void EmuThread::connectDisplaySignals(DisplayWidget* widget) @@ -2820,6 +2809,12 @@ void Host::ReleaseRenderWindow() g_emu_thread->releaseRenderWindow(); } +bool Host::CanChangeFullscreenMode(bool new_fullscreen_state) +{ + // Don't mess with fullscreen while locked. + return QtHost::IsSystemLocked(); +} + void EmuThread::updatePerformanceCounters(const GPUBackend* gpu_backend) { const RenderAPI render_api = g_gpu_device->GetRenderAPI(); diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index faa07e5af..5cee876c5 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -74,8 +74,6 @@ public: ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; } - ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; } - ALWAYS_INLINE InputDeviceListModel* getInputDeviceListModel() const { return m_input_device_list_model.get(); } std::optional acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, @@ -173,6 +171,7 @@ public: void redrawDisplayWindow(); void toggleFullscreen(); void setFullscreen(bool fullscreen); + void setFullscreenWithCompletionHandler(bool fullscreen, std::function completion_handler); void updateDisplayWindow(); void requestDisplaySize(float scale); void applyCheat(const QString& name); @@ -214,9 +213,8 @@ private: std::unique_ptr m_input_device_list_model; bool m_shutdown_flag = false; - bool m_is_fullscreen = false; - bool m_is_fullscreen_ui_started = false; bool m_gpu_thread_run_idle = false; + bool m_is_fullscreen_ui_started = false; float m_last_speed = std::numeric_limits::infinity(); float m_last_game_fps = std::numeric_limits::infinity(); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 2fd3f4a83..62cc0c349 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -417,16 +417,6 @@ void Host::RequestSystemShutdown(bool allow_confirm, bool save_state, bool check // } -bool Host::IsFullscreen() -{ - return false; -} - -void Host::SetFullscreen(bool enabled) -{ - // -} - std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, Error* error) { @@ -438,6 +428,11 @@ void Host::ReleaseRenderWindow() // } +bool Host::CanChangeFullscreenMode(bool new_fullscreen_state) +{ + return false; +} + void Host::BeginTextInput() { //