Move heavy SystemProperties loads to an opt-in model

This avoids heavyweight operations when we're not actually running config checks.
This commit is contained in:
Cameron Gutman
2026-01-26 19:46:49 -06:00
parent cd9aceb62c
commit d2fa488979
4 changed files with 82 additions and 51 deletions

View File

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

View File

@@ -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<QRect> monitorNativeResolutions;

View File

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

View File

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