From d2fa488979cfea9f97d26455fa8b385a727ec6a7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Jan 2026 19:46:49 -0600 Subject: [PATCH] Move heavy SystemProperties loads to an opt-in model This avoids heavyweight operations when we're not actually running config checks. --- app/backend/systemproperties.cpp | 96 +++++++++++++++++++------------- app/backend/systemproperties.h | 16 ++++-- app/gui/StreamSegue.qml | 5 ++ app/gui/main.qml | 16 ++++-- 4 files changed, 82 insertions(+), 51 deletions(-) diff --git a/app/backend/systemproperties.cpp b/app/backend/systemproperties.cpp index 7d52a827..b85a0951 100644 --- a/app/backend/systemproperties.cpp +++ b/app/backend/systemproperties.cpp @@ -98,51 +98,16 @@ SystemProperties::SystemProperties() hasDiscordIntegration = false; #endif - unmappedGamepads = SdlInputHandler::getUnmappedGamepads(); - // These will be queried asynchronously to avoid blocking the UI hasHardwareAcceleration = true; rendererAlwaysFullScreen = false; supportsHdr = true; maximumResolution = QSize(0, 0); - - // We initialize the video subsystem and test window on the main thread - // because some platforms (macOS) do not support window creation on - // non-main threads. - if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s", - SDL_GetError()); - return; - } - - // Update display related attributes (max FPS, native resolution, etc). - refreshDisplays(); - - testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, - SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags()); - if (!testWindow) { - SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, - "Failed to create test window with platform flags: %s", - SDL_GetError()); - - testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN); - if (!testWindow) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to create window for hardware decode test: %s", - SDL_GetError()); - SDL_QuitSubSystem(SDL_INIT_VIDEO); - return; - } - } - - systemPropertyQueryThread = new SystemPropertyQueryThread(this); - systemPropertyQueryThread->start(); } SystemProperties::~SystemProperties() { - systemPropertyQueryThread->wait(); + waitForAsyncLoad(); } void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr) @@ -177,7 +142,6 @@ void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, boo QRect SystemProperties::getNativeResolution(int displayIndex) { // Returns default constructed QRect if out of bounds - systemPropertyQueryThread->wait(); Q_ASSERT(!monitorNativeResolutions.isEmpty()); return monitorNativeResolutions.value(displayIndex); } @@ -185,7 +149,6 @@ QRect SystemProperties::getNativeResolution(int displayIndex) QRect SystemProperties::getSafeAreaResolution(int displayIndex) { // Returns default constructed QRect if out of bounds - systemPropertyQueryThread->wait(); Q_ASSERT(!monitorSafeAreaResolutions.isEmpty()); return monitorSafeAreaResolutions.value(displayIndex); } @@ -193,11 +156,66 @@ QRect SystemProperties::getSafeAreaResolution(int displayIndex) int SystemProperties::getRefreshRate(int displayIndex) { // Returns 0 if out of bounds - systemPropertyQueryThread->wait(); Q_ASSERT(!monitorRefreshRates.isEmpty()); return monitorRefreshRates.value(displayIndex); } +void SystemProperties::startAsyncLoad() +{ + if (systemPropertyQueryThread) { + // Already started/completed + return; + } + + // This isn't actually asynchronous (due to the need to synchronize with + // SdlGamepadKeyNavigation), but we don't query it in the constructor + // because it's expensive. + unmappedGamepads = SdlInputHandler::getUnmappedGamepads(); + if (!unmappedGamepads.isEmpty()) { + emit unmappedGamepadsChanged(); + } + + // We initialize the video subsystem and test window on the main thread + // because some platforms (macOS) do not support window creation on + // non-main threads. + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s", + SDL_GetError()); + return; + } + + // Update display related attributes (max FPS, native resolution, etc). + refreshDisplays(); + + testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, + SDL_WINDOW_HIDDEN | StreamUtils::getPlatformWindowFlags()); + if (!testWindow) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Failed to create test window with platform flags: %s", + SDL_GetError()); + + testWindow = SDL_CreateWindow("", 0, 0, 1280, 720, SDL_WINDOW_HIDDEN); + if (!testWindow) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to create window for hardware decode test: %s", + SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return; + } + } + + systemPropertyQueryThread = new SystemPropertyQueryThread(this); + systemPropertyQueryThread->start(); +} + +void SystemProperties::waitForAsyncLoad() +{ + if (systemPropertyQueryThread) { + systemPropertyQueryThread->wait(); + } +} + void SystemProperties::refreshDisplays() { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { diff --git a/app/backend/systemproperties.h b/app/backend/systemproperties.h index ebe040c1..b3859d12 100644 --- a/app/backend/systemproperties.h +++ b/app/backend/systemproperties.h @@ -26,18 +26,22 @@ public: Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT) Q_PROPERTY(QString versionString MEMBER versionString CONSTANT) - // Properties queried asynchronously + // Properties queried asynchronously (startAsyncLoad() must be called!) Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration NOTIFY hasHardwareAccelerationChanged) Q_PROPERTY(bool rendererAlwaysFullScreen MEMBER rendererAlwaysFullScreen NOTIFY rendererAlwaysFullScreenChanged) Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged) Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution NOTIFY maximumResolutionChanged) Q_PROPERTY(bool supportsHdr MEMBER supportsHdr NOTIFY supportsHdrChanged) - Q_INVOKABLE void refreshDisplays(); + // Either startAsyncLoad()+waitForAsyncLoad() or refreshDisplays() must be invoked first Q_INVOKABLE QRect getNativeResolution(int displayIndex); Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex); Q_INVOKABLE int getRefreshRate(int displayIndex); + Q_INVOKABLE void startAsyncLoad(); + Q_INVOKABLE void waitForAsyncLoad(); + Q_INVOKABLE void refreshDisplays(); + signals: void unmappedGamepadsChanged(); void hasHardwareAccelerationChanged(); @@ -49,8 +53,8 @@ private slots: void updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr); private: - QThread* systemPropertyQueryThread; - SDL_Window* testWindow; + QThread* systemPropertyQueryThread = nullptr; + SDL_Window* testWindow = nullptr; // Properties set by the constructor bool isRunningWayland; @@ -60,15 +64,15 @@ private: bool hasDesktopEnvironment; bool hasBrowser; bool hasDiscordIntegration; - QString unmappedGamepads; QString versionString; bool usesMaterial3Theme; - // Properties set by updateDecoderProperties() + // Properties only set if startAsyncLoad() is called bool hasHardwareAcceleration; bool rendererAlwaysFullScreen; QSize maximumResolution; bool supportsHdr; + QString unmappedGamepads; // Properties set by refreshDisplays() QList monitorNativeResolutions; diff --git a/app/gui/StreamSegue.qml b/app/gui/StreamSegue.qml index e590a987..377b5337 100644 --- a/app/gui/StreamSegue.qml +++ b/app/gui/StreamSegue.qml @@ -4,6 +4,7 @@ import QtQuick.Window 2.2 import SdlGamepadKeyNavigation 1.0 import Session 1.0 +import SystemProperties 1.0 Item { property Session session @@ -120,6 +121,10 @@ Item { session.sessionFinished.connect(sessionFinished) session.readyForDeletion.connect(sessionReadyForDeletion) + // Ensure the SystemProperties async thread is finished, + // since it may currently be using the SDL video subsystem + SystemProperties.waitForAsyncLoad() + // Kick off the stream spinnerTimer.start() streamLoader.active = true diff --git a/app/gui/main.qml b/app/gui/main.qml index 18fc3d7d..84e7d9de 100644 --- a/app/gui/main.qml +++ b/app/gui/main.qml @@ -56,13 +56,10 @@ ApplicationWindow { wow64Dialog.open() } - if (SystemProperties.unmappedGamepads) { - unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads - unmappedGamepadDialog.open() - } - - // Hardware acceleration is checked asynchronously + // Hardware acceleration and unmapped gamepads are checked asynchronously SystemProperties.hasHardwareAccelerationChanged.connect(hasHardwareAccelerationChanged) + SystemProperties.unmappedGamepadsChanged.connect(hasUnmappedGamepadsChanged) + SystemProperties.startAsyncLoad() } } @@ -77,6 +74,13 @@ ApplicationWindow { } } + function hasUnmappedGamepadsChanged() { + if (SystemProperties.unmappedGamepads) { + unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads + unmappedGamepadDialog.open() + } + } + // It would be better to use TextMetrics here, but it always lays out // the text slightly more compactly than real Text does in ToolTip, // causing unexpected line breaks to be inserted