Qt: Get rid of multiple sources of truth for fullscreen

Only has UI and CPU thread "views" now.
This commit is contained in:
Stenzek
2025-11-28 23:30:50 +10:00
parent b13207a02f
commit a5755d12f1
12 changed files with 105 additions and 113 deletions

View File

@@ -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);
});
}

View File

@@ -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)

View File

@@ -101,6 +101,7 @@ struct ALIGN_TO_CACHE_LINE State
WindowInfo render_window_info;
std::optional<GPURenderer> 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<u32> command_fifo_write_ptr{0};
@@ -538,10 +539,13 @@ bool GPUThread::Reconfigure(std::optional<GPURenderer> 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<GPUThreadReconfigureCommand>(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<GPURenderer> 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<bool>(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<GPURenderer> 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.

View File

@@ -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<WindowInfo> 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);

View File

@@ -73,10 +73,10 @@ struct GPUThreadReconfigureCommand : public GPUThreadCommand
Error* error_ptr;
bool* out_result;
std::optional<GPURenderer> renderer;
std::optional<bool> fullscreen;
std::optional<bool> start_fullscreen_ui;
GPUVSyncMode vsync_mode;
bool allow_present_throttle;
bool fullscreen;
bool force_recreate_device;
bool upload_vram;
GPUSettings settings;

View File

@@ -84,20 +84,6 @@ void RunOnCPUThread(std::function<void()> function, bool block = false);
/// Safely executes a function on the main/UI thread.
void RunOnUIThread(std::function<void()> function, bool block = false);
/// Called when the core is creating a render device.
/// This could also be fullscreen transition.
std::optional<WindowInfo> 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.

View File

@@ -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)"),

View File

@@ -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<WindowInfo> 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);

View File

@@ -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

View File

@@ -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<void()> 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<WindowInfo> 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();

View File

@@ -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<WindowInfo> 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<void()> completion_handler);
void updateDisplayWindow();
void requestDisplaySize(float scale);
void applyCheat(const QString& name);
@@ -214,9 +213,8 @@ private:
std::unique_ptr<InputDeviceListModel> 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<float>::infinity();
float m_last_game_fps = std::numeric_limits<float>::infinity();

View File

@@ -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<WindowInfo> 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()
{
//