From aa54ba2a05c293a095c41a0068c953e9f78b848d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 4 Nov 2026 23:11:35 -0600 Subject: [PATCH 1/7] Reject 10-bit and YUV 4:4:4 in the CUDA path Vulkan Video supports these on all Nvidia GPUs. --- app/streaming/video/ffmpeg-renderers/cuda.cpp | 7 ++++++- app/streaming/video/ffmpeg-renderers/cuda.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/streaming/video/ffmpeg-renderers/cuda.cpp b/app/streaming/video/ffmpeg-renderers/cuda.cpp index b238249367..89790933fa 100644 --- a/app/streaming/video/ffmpeg-renderers/cuda.cpp +++ b/app/streaming/video/ffmpeg-renderers/cuda.cpp @@ -16,10 +16,15 @@ CUDARenderer::~CUDARenderer() } } -bool CUDARenderer::initialize(PDECODER_PARAMETERS) +bool CUDARenderer::initialize(PDECODER_PARAMETERS params) { int err; + // This renderer only supports NV12 output + if (params->videoFormat & (VIDEO_FORMAT_MASK_10BIT | VIDEO_FORMAT_MASK_YUV444)) { + return false; + } + err = av_hwdevice_ctx_create(&m_HwContext, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 0); if (err != 0) { m_InitFailureReason = InitFailureReason::NoSoftwareSupport; diff --git a/app/streaming/video/ffmpeg-renderers/cuda.h b/app/streaming/video/ffmpeg-renderers/cuda.h index d58a56c724..60e3e5ab73 100644 --- a/app/streaming/video/ffmpeg-renderers/cuda.h +++ b/app/streaming/video/ffmpeg-renderers/cuda.h @@ -12,7 +12,7 @@ class CUDARenderer : public IFFmpegRenderer { public: CUDARenderer(); virtual ~CUDARenderer() override; - virtual bool initialize(PDECODER_PARAMETERS) override; + virtual bool initialize(PDECODER_PARAMETERS params) override; virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; virtual void renderFrame(AVFrame* frame) override; virtual bool isDirectRenderingSupported() override; From a6276aa8464ca792ef83fce22e9f915205d4bbe1 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 4 Nov 2026 23:17:28 -0600 Subject: [PATCH 2/7] Check for unbound texture before registering with CUDA --- app/streaming/video/ffmpeg-renderers/cuda.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/streaming/video/ffmpeg-renderers/cuda.cpp b/app/streaming/video/ffmpeg-renderers/cuda.cpp index 89790933fa..60b794f827 100644 --- a/app/streaming/video/ffmpeg-renderers/cuda.cpp +++ b/app/streaming/video/ffmpeg-renderers/cuda.cpp @@ -111,6 +111,11 @@ bool CUDAGLInteropHelper::registerBoundTextures() // Get the ID of this plane's texture glActiveTexture(GL_TEXTURE0 + i); glGetIntegerv(GL_TEXTURE_BINDING_2D, &tex); + if (tex == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No GL texture bound to slot %d", i); + err = 211; // CUDA_ERROR_NOT_MAPPED + goto Exit; + } // Register it with CUDA err = m_Funcs->cuGraphicsGLRegisterImage(&m_Resources[i], tex, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD); From 3fead131a6cb21f27d50bf9eb543b004d4a44f1f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 5 Nov 2026 01:55:26 -0600 Subject: [PATCH 3/7] Dynamically enable VK_KHR_internally_synchronized_queues for libplacebo v7.387.0 This is now disabled by default in libplacebo and must be explicitly enabled when the application knows that all other code sharing the device has been enlightened about the new queue synchronization flags. Since we know that Gamescope's WSI doesn't handle these new flags correctly, we will disable internally synchronized queues when that layer is present. See ValveSoftware/gamescope#2261 --- app/streaming/video/ffmpeg-renderers/plvk.cpp | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/streaming/video/ffmpeg-renderers/plvk.cpp b/app/streaming/video/ffmpeg-renderers/plvk.cpp index 07ccce8fff..48081e6f38 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.cpp +++ b/app/streaming/video/ffmpeg-renderers/plvk.cpp @@ -2,6 +2,7 @@ #include "streaming/session.h" #include "streaming/streamutils.h" +#include "utils.h" // Implementation in plvk_c.c #define PL_LIBAV_IMPLEMENTATION 0 @@ -375,6 +376,41 @@ bool PlVkRenderer::tryInitializeDevice(VkPhysicalDevice device, VkPhysicalDevice vkParams.extra_queues = VK_QUEUE_FLAG_BITS_MAX_ENUM; } +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(60, 31, 101) && defined(VK_KHR_internally_synchronized_queues) + // libplacebo needs explicit opt-in to use internally synchronized queues, since + // it requires cooperation with the app (ex: using vkGetDeviceQueue2). We know + // FFmpeg 9.0+ always wants internally synchronized queues if available, so we + // will turn it on when supported by the physical device. + if (isExtensionSupportedByPhysicalDevice(device, "VK_KHR_internally_synchronized_queues")) { + bool internallySynchronizedQueues; + if (!Utils::getEnvironmentVariableOverride("PLVK_INT_SYNC_QUEUES", &internallySynchronizedQueues)) { +#if LIBAVUTIL_VERSION_MAJOR < 62 + // The Gamescope WSI layer currently breaks apps using internally synchronized queues + // (failing to use vkGetDeviceQueue2), so we disable this feature when the Gamescope + // WSI layer is enabled. See gamescope#2261 for details. + internallySynchronizedQueues = (qEnvironmentVariableIntValue("ENABLE_GAMESCOPE_WSI") == 0 || + qEnvironmentVariableIntValue("DISABLE_GAMESCOPE_WSI") == 1); +#else + // We have no choice but to enable it since the queue locking callbacks are gone + internallySynchronizedQueues = true; +#endif + } + + static VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR featInternalSyncQueues = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INTERNALLY_SYNCHRONIZED_QUEUES_FEATURES_KHR, + nullptr, + internallySynchronizedQueues, + }; + + static const VkPhysicalDeviceFeatures2 features = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + (void*)&featInternalSyncQueues, + }; + + vkParams.features = &features; + } +#endif + { // Don't let Qt take DRM master from us during pl_vulkan_create() DrmMasterLocker locker; From 029da3fb01047b9f1e753d8214874e2fb0d0cf96 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 5 Nov 2026 18:07:26 -0600 Subject: [PATCH 4/7] Improve readability of longer hostnames Fixes #2064 --- app/gui/PcView.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/gui/PcView.qml b/app/gui/PcView.qml index e80135c150..1b2e74d8de 100644 --- a/app/gui/PcView.qml +++ b/app/gui/PcView.qml @@ -154,12 +154,18 @@ CenteredGridView { width: parent.width anchors.top: pcIcon.bottom anchors.bottom: parent.bottom - font.pointSize: 36 + font.pointSize: 30 horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap elide: Text.ElideRight } + // Display a tooltip with the full name if it's truncated + ToolTip.text: model.name + ToolTip.delay: 1000 + ToolTip.timeout: 5000 + ToolTip.visible: (hovered || highlighted) && pcNameText.truncated + Loader { id: pcContextMenuLoader asynchronous: true From 8cfc9437373fa59f142ae60ef3722ec575c25ce9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 5 Nov 2026 18:10:16 -0600 Subject: [PATCH 5/7] Update SDL3, sdl2-compat, and libplacebo SDL3 -> 3.4.20 sdl2-compat -> bebc6eb0 libplacebo -> v7.387.0 Fixes #2084 Fixes #2079 Fixes #2051 Fixes #2031 --- .github/workflows/build-appimage.yml | 6 +++--- setup-deps.ps1 | 2 +- setup-deps.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 4285c771c3..1ae272f613 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -42,7 +42,7 @@ jobs: uses: actions/checkout@v5 with: repository: libsdl-org/SDL - ref: release-3.4.18 + ref: release-3.4.20 path: deps/SDL - name: Build SDL3 @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v5 with: repository: libsdl-org/sdl2-compat - ref: release-2.32.74 + ref: bebc6eb01397497130ca58a1cb48c8bd141422f5 path: deps/sdl2-compat - name: Build sdl2-compat @@ -101,7 +101,7 @@ jobs: uses: actions/checkout@v5 with: repository: haasn/libplacebo - ref: 92b5ac6db79f4d680eb656692f7bf51e9606f42a + ref: v7.387.0 path: deps/libplacebo submodules: 'recursive' diff --git a/setup-deps.ps1 b/setup-deps.ps1 index bdb2bdbca7..0c6aedbc9c 100644 --- a/setup-deps.ps1 +++ b/setup-deps.ps1 @@ -4,7 +4,7 @@ $Organization = "moonlight-stream" $PrebuiltRepo = "moonlight-qt-deps" $TargetDir = Join-Path $PSScriptRoot "libs\windows" $Assets = @("windows-x64.zip", "windows-ARM64.zip") -$Tag = "v19" +$Tag = "v20" if (Test-Path $TargetDir) { Write-Host "Cleaning target directory..." -ForegroundColor Cyan diff --git a/setup-deps.py b/setup-deps.py index b713ee2bcd..c19547717d 100644 --- a/setup-deps.py +++ b/setup-deps.py @@ -7,7 +7,7 @@ ORGANIZATION = "moonlight-stream" PREBUILT_REPO = "moonlight-qt-deps" -TAG = "v19" +TAG = "v20" def get_platform_config(): system = platform.system() From 966638f91cf686029341f9e2532395c8f6f485bb Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 5 Nov 2026 18:41:51 -0600 Subject: [PATCH 6/7] Remove libplacebo patch that's no longer needed Upstream libplacebo make internally synchronized queues opt-in. --- .github/workflows/build-appimage.yml | 1 - ...ebo-disable-internally-synchronized-queues.patch | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 app/deploy/linux/appimage/libplacebo-disable-internally-synchronized-queues.patch diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 1ae272f613..300ac584ce 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -108,7 +108,6 @@ jobs: - name: Build libplacebo working-directory: deps/libplacebo run: | - git apply ../../app/deploy/linux/appimage/*.patch meson setup build -Dvulkan=enabled -Dopengl=disabled -Ddemos=false ninja -C build sudo ninja install -C build diff --git a/app/deploy/linux/appimage/libplacebo-disable-internally-synchronized-queues.patch b/app/deploy/linux/appimage/libplacebo-disable-internally-synchronized-queues.patch deleted file mode 100644 index 0aaa10ec45..0000000000 --- a/app/deploy/linux/appimage/libplacebo-disable-internally-synchronized-queues.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/vulkan/context.c b/src/vulkan/context.c -index bc9084ad..52290154 100644 ---- a/src/vulkan/context.c -+++ b/src/vulkan/context.c -@@ -265,7 +265,7 @@ static_assert(PL_ARRAY_SIZE(pl_vulkan_recommended_extensions) + 1 == - #ifdef VK_KHR_internally_synchronized_queues - static const VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR synchronized_queues = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INTERNALLY_SYNCHRONIZED_QUEUES_FEATURES_KHR, -- .internallySynchronizedQueues = true, -+ .internallySynchronizedQueues = false, - }; - #endif - From 4113dd26be0907f2edf1805c7b9fd23766e17161 Mon Sep 17 00:00:00 2001 From: Joel <43355957+joelwilsonmt@users.noreply.github.com> Date: Thu, 5 Nov 2026 18:12:12 -0700 Subject: [PATCH 7/7] Fix toolbar button resize loop when moving between displays (#2093) NavigableToolButton bound its icon size to its background. Material's ToolButton background is half the button height while the content is square, but spans the full button width otherwise. When the window straddles a 2x and a 1x display, the header row briefly lays out at a smaller height, the content stops being square, and the icon width becomes the button width. The button's implicit width is then icon + padding, so every layout pass widens it by 12px without end and reloads the SVG icon at each new size. The UI hangs and memory grows until the process is killed. Size the icon from the button's own height instead. That height comes from Layout.preferredHeight, not from the icon, so there's no feedback loop, and the icon stays 30x30 in the 60px toolbar as before. Fixes #2092 --- app/gui/NavigableToolButton.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/gui/NavigableToolButton.qml b/app/gui/NavigableToolButton.qml index 9839196cbe..97c0ea907e 100644 --- a/app/gui/NavigableToolButton.qml +++ b/app/gui/NavigableToolButton.qml @@ -8,8 +8,12 @@ ToolButton { activeFocusOnTab: true icon.source: iconSource - icon.width: background.width - icon.height: background.height + // Size the icon from the button's height rather than its background. + // Material's background spans the full button width when the content + // isn't square, so binding the icon to it feeds the button's width back + // into its own implicit width and the toolbar layout grows without bound. + icon.width: height / 2 + icon.height: height / 2 // This determines the size of the Material highlight. We increase it // from the default because we use larger than normal icons for TV readability.