From 0d2830bb1b971304711cd4026727d8db6b4d019b Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Sat, 18 Apr 2026 21:03:26 +0100 Subject: [PATCH 1/3] Revert "fix(linux/xdgportal): avoid duplicate frame insertion (#4839)" This reverts commit 99d4e053bf6bee4f4a3dfa217b608600cf42a79c. --- src/platform/common.h | 4 ---- src/platform/linux/pipewire.cpp | 5 ----- src/video.cpp | 5 ++--- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index 7a15cb09cf0..0777a50647c 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -535,10 +535,6 @@ namespace platf { return true; } - virtual bool is_event_driven() { - return false; - } - virtual ~display_t() = default; // Offsets for when streaming a specific monitor. By default, they are 0. diff --git a/src/platform/linux/pipewire.cpp b/src/platform/linux/pipewire.cpp index e02a2b76ca0..a6a7ab6c97b 100644 --- a/src/platform/linux/pipewire.cpp +++ b/src/platform/linux/pipewire.cpp @@ -858,11 +858,6 @@ namespace pipewire { return 0; } - // This capture method is event driven; don't insert duplicate frames - bool is_event_driven() override { - return true; - } - private: bool is_buffer_redundant(const egl::img_descriptor_t *img) { // Check for corrupted frame diff --git a/src/video.cpp b/src/video.cpp index 17106133f7c..713d85f98ab 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2048,9 +2048,8 @@ namespace video { } }); - // set max frame time based on client-requested target framerate (or 0.5fps/2000ms for event-driven capture) - double def_fps_target = (disp->is_event_driven() ? 1 : config.framerate); - double minimum_fps_target = (config::video.minimum_fps_target > 0.0) ? config::video.minimum_fps_target : def_fps_target; + // set max frame time based on client-requested target framerate. + double minimum_fps_target = (config::video.minimum_fps_target > 0.0) ? config::video.minimum_fps_target : config.framerate; std::chrono::duration max_frametime {1000.0 / minimum_fps_target}; BOOST_LOG(info) << "Minimum FPS target set to ~"sv << (minimum_fps_target / 2) << "fps ("sv << max_frametime.count() * 2 << "ms)"sv; From ddcb02db5108c577ef23873c9c911ecd1ecaf053 Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Mon, 13 Apr 2026 13:36:15 +0100 Subject: [PATCH 2/3] fix: resolve timed pop() resetting timeout on spurious wakeups The previous implementation called wait_for(delay) inside a manual loop, causing the full timeout to reset on each spurious wakeup. Replace with a single predicate-based wait_for call, which handles spurious wakeups internally while correctly honoring the original deadline. --- src/thread_safe.h | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/thread_safe.h b/src/thread_safe.h index 9657de06371..1ef8f336b80 100644 --- a/src/thread_safe.h +++ b/src/thread_safe.h @@ -60,22 +60,19 @@ namespace safe { } // pop and view should not be used interchangeably - template + template status_t pop(std::chrono::duration delay) { std::unique_lock ul {_lock}; - if (!_continue) { + if (bool success = _cv.wait_for(ul, delay, [this] { + return (bool) _status || !_continue; + }); + !success || !_continue) { return util::false_v; } - while (!_status) { - if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) { - return util::false_v; - } - } - auto val = std::move(_status); - _status = util::false_v; + _status.reset(); return val; } From 3f71759de6d70767869615e2817d845357da4394 Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Mon, 13 Apr 2026 23:28:31 +0100 Subject: [PATCH 3/3] fix(video): set correct default minimum_fps_target and max_frametime interval alignment The current minimum_fps_target and max_frametime intervals are set/logged incorrectly and break capture methods on Linux. Example with a 60fps stream: * default minimum_fps_target is populated as config.framerate (60), * minimum FPS target is logged as 30 and interval logged as 33.33ms, but pop() interval is actually 16.6ms. * result: default value is 2x what documentation implies should be the case. Setting a manual value logs the target FPS and interval incorrectly but sets the correct target interval for the duplicate insertion logic via pop(). Issues observed at 60fps: * kmsgrab: fps cap violations and stuttering during cursor shape changes due to increased host processing latency triggering extra duplicates. * portalgrab: idle desktop settles on ~52fps instead of the correct 30fps default target. * portalgrab: massive framerate bursting due to irregular frame arrival that manifests at specific framerates; example: "strangle 45 glxgears" produces sustained 90fps on a 60fps stream. Can also manifest when framerate = stream framerate, but is harder to reproduce. New behaviour: * minimum_fps_target defaults to (config.framerate / 2), and max_frametime aligns with the actual minimum target FPS. This makes the logging accurate and the existing documention becomes sensible. * kmsgrab: during expensive cursor updates, no more framerate violations or stuttering. * portalgrab: desktop idles at 30fps instead of ~52fps. * portalgrab: bursting is resolved. No more violations at 45fps, 60fps, etc. --- src/video.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 713d85f98ab..d8c9dde3125 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2049,9 +2049,9 @@ namespace video { }); // set max frame time based on client-requested target framerate. - double minimum_fps_target = (config::video.minimum_fps_target > 0.0) ? config::video.minimum_fps_target : config.framerate; + double minimum_fps_target = (config::video.minimum_fps_target > 0.0) ? config::video.minimum_fps_target : (config.framerate / 2); std::chrono::duration max_frametime {1000.0 / minimum_fps_target}; - BOOST_LOG(info) << "Minimum FPS target set to ~"sv << (minimum_fps_target / 2) << "fps ("sv << max_frametime.count() * 2 << "ms)"sv; + BOOST_LOG(info) << "Minimum FPS target set to ~"sv << minimum_fps_target << "fps ("sv << max_frametime.count() << "ms)"sv; auto shutdown_event = mail->event(mail::shutdown); auto packets = mail::man->queue(mail::video_packets);