From fad197fdce9895bd96f2c1a8f32f853e7d55fb8d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 8 Jan 2026 19:09:54 -0600 Subject: [PATCH] Move heavy SystemProperties initialization operations off the main thread This dramatically improves startup performance on slow devices. --- app/backend/systemproperties.cpp | 143 +++++++++++++++++++++---------- app/backend/systemproperties.h | 42 ++++++--- app/gui/main.qml | 20 +++-- 3 files changed, 140 insertions(+), 65 deletions(-) diff --git a/app/backend/systemproperties.cpp b/app/backend/systemproperties.cpp index 73e67c11..13368fc1 100644 --- a/app/backend/systemproperties.cpp +++ b/app/backend/systemproperties.cpp @@ -12,6 +12,69 @@ #include #endif +class SystemPropertyQueryThread : public QThread +{ +public: + SystemPropertyQueryThread(SystemProperties* properties) + : QThread(properties), m_Properties(properties) + { + setObjectName("System Properties Async Query Thread"); + } + +private: + void run() override + { + bool hasHardwareAcceleration; + bool rendererAlwaysFullScreen; + bool supportsHdr; + QSize maximumResolution; + + 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). + m_Properties->refreshDisplays(); + + SDL_Window* 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; + } + } + + Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution); + + SDL_DestroyWindow(testWindow); + + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + // Propagate the decoder properties to the SystemProperties singleton and emit any change signals on the main thread + QMetaObject::invokeMethod(m_Properties, "updateDecoderProperties", + Qt::QueuedConnection, + Q_ARG(bool, hasHardwareAcceleration), + Q_ARG(bool, rendererAlwaysFullScreen), + Q_ARG(QSize, maximumResolution), + Q_ARG(bool, supportsHdr)); + } + +private: + SystemProperties* m_Properties; +}; + SystemProperties::SystemProperties() { versionString = QString(VERSION_STR); @@ -68,71 +131,63 @@ SystemProperties::SystemProperties() unmappedGamepads = SdlInputHandler::getUnmappedGamepads(); - // Populate data that requires talking to SDL. We do it all in one shot - // and cache the results to speed up future queries on this data. - querySdlVideoInfo(); + // These will be queried asynchronously to avoid blocking the UI + hasHardwareAcceleration = true; + rendererAlwaysFullScreen = false; + supportsHdr = true; + maximumResolution = QSize(0, 0); - Q_ASSERT(!monitorRefreshRates.isEmpty()); - Q_ASSERT(!monitorNativeResolutions.isEmpty()); - Q_ASSERT(!monitorSafeAreaResolutions.isEmpty()); + systemPropertyQueryThread = new SystemPropertyQueryThread(this); + systemPropertyQueryThread->start(); +} + +void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr) +{ + if (hasHardwareAcceleration != this->hasHardwareAcceleration) { + this->hasHardwareAcceleration = hasHardwareAcceleration; + emit hasHardwareAccelerationChanged(); + } + + if (rendererAlwaysFullScreen != this->rendererAlwaysFullScreen) { + this->rendererAlwaysFullScreen = rendererAlwaysFullScreen; + emit rendererAlwaysFullScreenChanged(); + } + + if (maximumResolution != this->maximumResolution) { + this->maximumResolution = maximumResolution; + emit maximumResolutionChanged(); + } + + if (supportsHdr != this->supportsHdr) { + this->supportsHdr = supportsHdr; + emit supportsHdrChanged(); + } } QRect SystemProperties::getNativeResolution(int displayIndex) { // Returns default constructed QRect if out of bounds + systemPropertyQueryThread->wait(); + Q_ASSERT(!monitorNativeResolutions.isEmpty()); return monitorNativeResolutions.value(displayIndex); } QRect SystemProperties::getSafeAreaResolution(int displayIndex) { // Returns default constructed QRect if out of bounds + systemPropertyQueryThread->wait(); + Q_ASSERT(!monitorSafeAreaResolutions.isEmpty()); return monitorSafeAreaResolutions.value(displayIndex); } int SystemProperties::getRefreshRate(int displayIndex) { // Returns 0 if out of bounds + systemPropertyQueryThread->wait(); + Q_ASSERT(!monitorRefreshRates.isEmpty()); return monitorRefreshRates.value(displayIndex); } -void SystemProperties::querySdlVideoInfo() -{ - hasHardwareAcceleration = false; - - 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(); - - SDL_Window* 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; - } - } - - Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution); - - SDL_DestroyWindow(testWindow); - - SDL_QuitSubSystem(SDL_INIT_VIDEO); -} - void SystemProperties::refreshDisplays() { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { diff --git a/app/backend/systemproperties.h b/app/backend/systemproperties.h index fd2029c3..fe718a34 100644 --- a/app/backend/systemproperties.h +++ b/app/backend/systemproperties.h @@ -7,14 +7,12 @@ class SystemProperties : public QObject { Q_OBJECT - friend class QuerySdlVideoThread; - friend class RefreshDisplaysThread; + friend class SystemPropertyQueryThread; public: SystemProperties(); - Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration CONSTANT) - Q_PROPERTY(bool rendererAlwaysFullScreen MEMBER rendererAlwaysFullScreen CONSTANT) + // Static properties queried synchronously during the constructor Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT) Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT) Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT) @@ -22,11 +20,15 @@ public: Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT) Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT) Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT) - Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged) - Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT) - Q_PROPERTY(QString versionString MEMBER versionString CONSTANT) - Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT) Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT) + Q_PROPERTY(QString versionString MEMBER versionString CONSTANT) + + // Properties queried asynchronously + 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(); Q_INVOKABLE QRect getNativeResolution(int displayIndex); @@ -35,12 +37,18 @@ public: signals: void unmappedGamepadsChanged(); + void hasHardwareAccelerationChanged(); + void rendererAlwaysFullScreenChanged(); + void maximumResolutionChanged(); + void supportsHdrChanged(); + +private slots: + void updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr); private: - void querySdlVideoInfo(); + QThread* systemPropertyQueryThread; - bool hasHardwareAcceleration; - bool rendererAlwaysFullScreen; + // Properties set by the constructor bool isRunningWayland; bool isRunningXWayland; bool isWow64; @@ -49,12 +57,18 @@ private: bool hasBrowser; bool hasDiscordIntegration; QString unmappedGamepads; + QString versionString; + bool usesMaterial3Theme; + + // Properties set by updateDecoderProperties() + bool hasHardwareAcceleration; + bool rendererAlwaysFullScreen; QSize maximumResolution; + bool supportsHdr; + + // Properties set by refreshDisplays() QList monitorNativeResolutions; QList monitorSafeAreaResolutions; QList monitorRefreshRates; - QString versionString; - bool supportsHdr; - bool usesMaterial3Theme; }; diff --git a/app/gui/main.qml b/app/gui/main.qml index dc50c3a0..e9a581b4 100644 --- a/app/gui/main.qml +++ b/app/gui/main.qml @@ -54,7 +54,18 @@ ApplicationWindow { if (SystemProperties.isWow64) { wow64Dialog.open() } - else if (!SystemProperties.hasHardwareAcceleration && StreamingPreferences.videoDecoderSelection !== StreamingPreferences.VDS_FORCE_SOFTWARE) { + + if (SystemProperties.unmappedGamepads) { + unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads + unmappedGamepadDialog.open() + } + + // Hardware acceleration is checked asynchronously + SystemProperties.hasHardwareAccelerationChanged.connect(hasHardwareAccelerationChanged) + } + + function hasHardwareAccelerationChanged() { + if (!SystemProperties.hasHardwareAcceleration && StreamingPreferences.videoDecoderSelection !== StreamingPreferences.VDS_FORCE_SOFTWARE) { if (SystemProperties.isRunningXWayland) { xWaylandDialog.open() } @@ -62,13 +73,8 @@ ApplicationWindow { noHwDecoderDialog.open() } } - - 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