Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Off-screen windows are not refreshed for screencasts #539

Open
sodiboo opened this issue Jul 10, 2024 · 0 comments · May be fixed by #642
Open

Off-screen windows are not refreshed for screencasts #539

sodiboo opened this issue Jul 10, 2024 · 0 comments · May be fixed by #642
Labels
bug Something isn't working

Comments

@sodiboo
Copy link
Contributor

sodiboo commented Jul 10, 2024

Description

When screencasting windows that aren't visible (e.g. scrolled far away, or on a different workspace entirely), they are only refreshed at 1Hz. This is because of ed8a6af. Prior to that commit, they wouldn't be refreshed at all. Windows that aren't visible aren't refreshed every frame, because that's wasteful. This calculation doesn't take into consideration the new per-window screencasting introduced just before 0.1.7 was tagged. So, those captured windows, when on a separate workspace or otherwise not within the current viewport, look really stuttery on a screencast. But they should be considered as visible. This likely went unnoticed due to testing windowed screencasting on mostly static windows that change when interacted with (and always have focus as a result). To reproduce it, play a youtube video, and open Discord on a separate workspace. Screenshare that browser tab in Discord and observe the stuttery mess it becomes.

Discovery

Originally discovered by @yuu-fur (@yuu.dev:catgirl.cloud), who uses a laptop with a NVIDIA-brand dedicated GPU and a hybrid setup. We both thought it might be an NVIDIA issue, but it turned out not to be the case. Changing which GPU anything runs on doesn't help. I later reproduced the issue on my own system, which runs an AMD GPU, completely ruling out the possibility of it being an NVIDIA issue. Since this was happening on Vesktop, i thought it might be Vencord/Vesktop#629 which is caused by an underlying Chromium bug, but his issue persists in Firefox-based browsers too (Librewolf; tested with web Discord) so that's not it either. We eventually realized it was highly correlated to window focus, and i remembered that commit from way back about refreshing off-screen windows, and i tested various ways for a window to leave the viewport, all of which resulted in the same stutter. This led me to look into the underlying code.

Workaround

Do not use this workaround. It wastes resources by refreshing every window every frame. A real patch that only refreshes the relevant screencapture windows is available. You can expand this to see what i originally had to say about the workaround.

I wrote this patch to workaround the issue. It does not fundamentally solve the problem, because it just increases the refresh rate of off-screen windows. You can change out the / 60 with whatever your refresh rate is. This patch will cause more resource usage, including degraded battery life. It basically disables a power-saving feature (or rather, heavily nerfs it). I created it by looking at ed8a6af and just changing the timer values. A Duration::ZERO is valid for how this is used. I didn't try this on my own system, but @yuu-fur did and reports that it worked well and seems to solve the issue (yay! high-refresh-rate screencasting!)

I don't recommend anyone really run this patch, especially not on a battery-constrained device (think of your battery life! your poor battery!), but it basically confirms the root cause is what i described at the top. You can apply it in my niri-flake by saving this patch to a file niri.patch and then setting programs.niri.package = pkgs.niri-unstable.override { patches = [ ./niri.patch ]; };

diff --git a/src/niri.rs b/src/niri.rs
index 664aea6..9ae8505 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -147,7 +147,7 @@ const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
 // We'll try to send frame callbacks at least once a second. We'll make a timer that fires once a
 // second, so with the worst timing the maximum interval between two frame callbacks for a surface
 // should be ~1.995 seconds.
-const FRAME_CALLBACK_THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
+const FRAME_CALLBACK_THROTTLE: Option<Duration> = Some(Duration::ZERO);
 
 pub struct Niri {
     pub config: Rc<RefCell<Config>>,
@@ -1607,10 +1607,10 @@ impl Niri {
 
         event_loop
             .insert_source(
-                Timer::from_duration(Duration::from_secs(1)),
+                Timer::from_duration(Duration::from_secs(1) / 60),
                 |_, _, state| {
                     state.niri.send_frame_callbacks_on_fallback_timer();
-                    TimeoutAction::ToDuration(Duration::from_secs(1))
+                    TimeoutAction::ToDuration(Duration::from_secs(1) / 60)
                 },
             )
             .unwrap();

Patch

System Information

  • Distro: NixOS 24.05 Uakari & NixOS 24.11 Vicuña
  • GPU: NVIDIA GeForce GTX 1650 Mobile & AMD Radeon RX 7800 XT
  • CPU: Intel Core i7-9750H & AMD Ryzen 5 7600X

how many times can you mention your favorite commit in just one issue?
@sodiboo sodiboo added the bug Something isn't working label Jul 10, 2024
@sodiboo sodiboo linked a pull request Aug 31, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant