From 64fea80ac95f607f9c0c5d74dbb02d6b67133190 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 25 Jan 2026 17:10:04 -0600 Subject: [PATCH] Create the SystemProperties test window on the main thread --- app/backend/systemproperties.cpp | 74 +++++++++++-------- app/backend/systemproperties.h | 4 + .../video/ffmpeg-renderers/eglvid.cpp | 2 +- .../video/ffmpeg-renderers/sdlvid.cpp | 8 ++ .../ffmpeg-renderers/vt_avsamplelayer.mm | 10 +-- .../video/ffmpeg-renderers/vt_metal.mm | 51 +++++++------ 6 files changed, 89 insertions(+), 60 deletions(-) diff --git a/app/backend/systemproperties.cpp b/app/backend/systemproperties.cpp index 13368fc1..7d52a827 100644 --- a/app/backend/systemproperties.cpp +++ b/app/backend/systemproperties.cpp @@ -29,38 +29,7 @@ private: 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); + Session::getDecoderInfo(m_Properties->testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution); // Propagate the decoder properties to the SystemProperties singleton and emit any change signals on the main thread QMetaObject::invokeMethod(m_Properties, "updateDecoderProperties", @@ -137,12 +106,49 @@ SystemProperties::SystemProperties() 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(); +} + void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr) { + SDL_assert(testWindow); + if (hasHardwareAcceleration != this->hasHardwareAcceleration) { this->hasHardwareAcceleration = hasHardwareAcceleration; emit hasHardwareAccelerationChanged(); @@ -162,6 +168,10 @@ void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, boo this->supportsHdr = supportsHdr; emit supportsHdrChanged(); } + + SDL_DestroyWindow(testWindow); + testWindow = nullptr; + SDL_QuitSubSystem(SDL_INIT_VIDEO); } QRect SystemProperties::getNativeResolution(int displayIndex) diff --git a/app/backend/systemproperties.h b/app/backend/systemproperties.h index fe718a34..ebe040c1 100644 --- a/app/backend/systemproperties.h +++ b/app/backend/systemproperties.h @@ -3,6 +3,8 @@ #include #include +#include "SDL_compat.h" + class SystemProperties : public QObject { Q_OBJECT @@ -11,6 +13,7 @@ class SystemProperties : public QObject public: SystemProperties(); + ~SystemProperties(); // Static properties queried synchronously during the constructor Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT) @@ -47,6 +50,7 @@ private slots: private: QThread* systemPropertyQueryThread; + SDL_Window* testWindow; // Properties set by the constructor bool isRunningWayland; diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.cpp b/app/streaming/video/ffmpeg-renderers/eglvid.cpp index 5bf5a2d0..7e7524c2 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/eglvid.cpp @@ -464,7 +464,7 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params) // to ensure we don't drop any important events. session->flushWindowEvents(); } - else { + else if (!params->testOnly) { // If we get here prior to the start of a session, just pump and flush ourselves. SDL_PumpEvents(); SDL_FlushEvent(SDL_WINDOWEVENT); diff --git a/app/streaming/video/ffmpeg-renderers/sdlvid.cpp b/app/streaming/video/ffmpeg-renderers/sdlvid.cpp index ebfd7886..babb97f6 100644 --- a/app/streaming/video/ffmpeg-renderers/sdlvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/sdlvid.cpp @@ -140,6 +140,14 @@ bool SdlRenderer::initialize(PDECODER_PARAMETERS params) return false; } + // Don't create a renderer or pump events for test-only + // renderers. Test-only renderers might be created on + // a non-main thread where interaction with the SDL + // render API is unsafe. + if (params->testOnly) { + return true; + } + SDL_SysWMinfo info; SDL_VERSION(&info.version); if (!SDL_GetWindowWMInfo(params->window, &info)) { diff --git a/app/streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm b/app/streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm index 8858e09a..5d43bfb3 100644 --- a/app/streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm +++ b/app/streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm @@ -100,11 +100,11 @@ public: if (m_DisplayLayer != nullptr) { [m_DisplayLayer release]; - } - // It appears to be necessary to run the event loop after destroying - // the AVSampleBufferDisplayLayer to avoid issue #973. - SDL_PumpEvents(); + // It appears to be necessary to run the event loop after destroying + // the AVSampleBufferDisplayLayer to avoid issue #973. + SDL_PumpEvents(); + } }} static @@ -333,7 +333,7 @@ public: } // If we're using direct rendering, set up the AVSampleBufferDisplayLayer - if (m_DirectRendering) { + if (m_DirectRendering && !params->testOnly) { SDL_SysWMinfo info; SDL_VERSION(&info.version); diff --git a/app/streaming/video/ffmpeg-renderers/vt_metal.mm b/app/streaming/video/ffmpeg-renderers/vt_metal.mm index 7182b239..12eb7df2 100644 --- a/app/streaming/video/ffmpeg-renderers/vt_metal.mm +++ b/app/streaming/video/ffmpeg-renderers/vt_metal.mm @@ -658,30 +658,11 @@ public: return false; } - m_MetalView = SDL_Metal_CreateView(m_Window); - if (!m_MetalView) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_Metal_CreateView() failed: %s", - SDL_GetError()); - return false; - } - - m_MetalLayer = (CAMetalLayer*)SDL_Metal_GetLayer(m_MetalView); - - // Choose a device - m_MetalLayer.device = device; - - // Allow EDR content if we're streaming in a 10-bit format - m_MetalLayer.wantsExtendedDynamicRangeContent = !!(params->videoFormat & VIDEO_FORMAT_MASK_10BIT); - - // Allow tearing if V-Sync is off (also requires direct display path) - m_MetalLayer.displaySyncEnabled = params->enableVsync; - // Create the Metal texture cache for our CVPixelBuffers CFStringRef keys[1] = { kCVMetalTextureUsage }; NSUInteger values[1] = { MTLTextureUsageShaderRead }; auto cacheAttributes = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1, nullptr, nullptr); - err = CVMetalTextureCacheCreate(kCFAllocatorDefault, cacheAttributes, m_MetalLayer.device, nullptr, &m_TextureCache); + err = CVMetalTextureCacheCreate(kCFAllocatorDefault, cacheAttributes, device, nullptr, &m_TextureCache); CFRelease(cacheAttributes); if (err != kCVReturnSuccess) { @@ -693,7 +674,7 @@ public: // Compile our shaders QString shaderSource = QString::fromUtf8(Path::readDataFile("vt_renderer.metal")); - m_ShaderLibrary = [m_MetalLayer.device newLibraryWithSource:shaderSource.toNSString() options:nullptr error:nullptr]; + m_ShaderLibrary = [device newLibraryWithSource:shaderSource.toNSString() options:nullptr error:nullptr]; if (!m_ShaderLibrary) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile shaders"); @@ -701,7 +682,33 @@ public: } // Create a command queue for submission - m_CommandQueue = [m_MetalLayer.device newCommandQueue]; + m_CommandQueue = [device newCommandQueue]; + + // Add the Metal view to the window if we're not in test-only mode + // + // NB: Test-only renderers may be created on a non-main thread, so + // we don't want to touch the view hierarchy in that context. + if (!params->testOnly) { + m_MetalView = SDL_Metal_CreateView(m_Window); + if (!m_MetalView) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_Metal_CreateView() failed: %s", + SDL_GetError()); + return false; + } + + m_MetalLayer = (CAMetalLayer*)SDL_Metal_GetLayer(m_MetalView); + + // Choose a device + m_MetalLayer.device = device; + + // Allow EDR content if we're streaming in a 10-bit format + m_MetalLayer.wantsExtendedDynamicRangeContent = !!(params->videoFormat & VIDEO_FORMAT_MASK_10BIT); + + // Allow tearing if V-Sync is off (also requires direct display path) + m_MetalLayer.displaySyncEnabled = params->enableVsync; + } + return true; }}