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

Use wlr_frame_scheduler #7780

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

emersion
Copy link
Member

@@ -747,7 +746,6 @@ static void handle_frame(struct wl_listener *listener, void *user_data) {
if (delay < 1) {
output_repaint_timer_handler(output);
} else {
output->wlr_output->frame_pending = true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Draft because dropping this bool flag isn't the right thing to do. We probably want to implement our own custom frame scheduler instead.

@GrabbenD
Copy link

This is impressive! 🙂

I know it's still work in progress but I found some interesting takeaways after comparing it back and forth a couple of times.

In comparison to Sway 1.8.1, here's bugs which are not present in the current state of this PR:

  • Direct Scanout doesn't have to be disabled to workaround VRR being stuck at output's refresh rate (bug: #7370).
  • Mouse movement speed/polling rate doesn't affect the displayed frame rate thus VRR doesn't break when moving the cursor (bug: #6832).

Differences:

  • There's a performance uplift, from 96 FPS to 100 FPS with Halo Infinite (in comparison to Sway 1.9.0-dev swaywm/sway@4326a26 + Wlroots 0.17.0-dev wlroots/wlroots@47bf87ad).
  • Poorly optimized games with high output latency like Halo Infinite are noticeably smoother due to less judder.

Problems

  • Cursor pane is only updated when there's new frames being displayed (even with VRR disabled) meaning you can't move the mouse whilst in desktop with no open windows (for reference I'm not using a background/swagbg nor any bar/swaybar)

Setup:

I was hoping to provide more comprehensive numbers with GPUVIS but I haven't figured out good filters to make sense of the data.

@emersion
Copy link
Member Author

That sounds like unintended consequences. In theory once everything is properly implemented these changes shouldn't change the current behavior at all.

@emersion
Copy link
Member Author

This is now feature-complete.

@emersion emersion marked this pull request as ready for review December 15, 2023 16:05
@GrabbenD
Copy link

Christmas came early this year!
WINE 9.0 with Wayland Vulkan driver and improved scheduler in Sway/Wlroots, can it get any better? 😄

I've been using this for a couple of hours now and I'm very happy with how it works.
Sway was the smoothest Wayland compositor way before this PR and it just keeps on getting better!

Feedback

My setup

Specifications:

  • Hardware: same as above (Use wlr_frame_scheduler #7780 (comment))
  • Kernel: 6.6.6
  • sway-git: r7232.92ad6641-1
  • wlroots-git: 1:0.18.0.r6796.ecc134a6-1
  • mesa-git: 24.0.0_devel.181987.9ab59574ef1-1
  • My monitor has OSD which shows the current refresh rate

Kernel arguments:

# AMD (EEP)
'amd_pstate.shared_mem=1'
'amd_pstate=active'
'amd_prefcore=enable'

# GPU (VRR)
'video=DP-1:3840x1600@144'
'amdgpu.freesync_video=1'

# Performance
'mitigations=off'
'gpu_sched.sched_policy=0' # https://gitlab.freedesktop.org/drm/amd/-/issues/2516#note_2119750
'preempt=full'

What has changed?

In comparison to Sway 1.8.1:

Improvements:

Noteworthy:

  • VSync needs to be activated in game for VRR to even be triggered.

  • I can no longer seem to find a way to use VRR when watching fullscreen videos in Chromium based browsers like Brave (this never made a noticeable difference though).

Problem 1 - new "Failed to page-flip output" (error)

  1. Start sway (without any environment variables):

    $ sway |& tee sway.log
    
  2. Launch any program (like the foot terminal or just VSCodium)
    This spams Failed to page-flip output error which is repeating every time I move the mouse cursor around:

    00:00:00.369 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:01.195 [ERROR] [wlr] [types/wlr_layer_shell_v1.c:295] A configure is sent to an uninitialized wlr_layer_surface_v1 0x55b34e7b06c0
    00:00:01.196 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:01.488 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:03.064 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:04.329 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:04.329 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    
  • It doesn't matter if option adaptive_sync on or adaptive_sync off is used.

  • I couldn't identify a way of stopping this error (e.g.-Dnoscanout, WLR_DRM_NO_ATOMIC=1 nor WLR_DRM_NO_MODIFIERS=1).

Problem 2 - can't toggle VRR on-the-fly (crash)

Toggling VRR causes compositor to crash instantly even though VRR is successfully activated.

  • Here's my display config:

    # .config/sway/config
    output DP-1 {
       mode 3840x1600@143.998Hz
       position 0 0
       adaptive_sync off
       max_render_time 1
    }
  • If I execute $ swaymsg output DP-1 adaptive_sync 1 the compositor will exit instantly (and this is the entire sway.log):

    00:00:00.376 [ERROR] [wlr] [backend/drm/drm.c:807] connector DP-1: Failed to page-flip output: a page-flip is already pending
    00:00:02.380 [ERROR] [wlr] [types/wlr_layer_shell_v1.c:295] A configure is sent to an uninitialized wlr_layer_surface_v1 0x563ed38aec00
    
  • Same issue happens when using adaptive_sync on in config and executing: $ swaymsg output DP-1 adaptive_sync 0

Problem 3 - refresh rate isn't always updated

Monitor continues to use the latest refresh rate instead of using the one specified in the config file when following these steps:

  1. Start $ sway with adaptive_sync off config option.

  2. Enable VRR: $ swaymsg output DP-1 adaptive_sync 1 (which causes a crash even though VRR gets activated).

  3. Since only one frame was displayed before the crash, the monitor uses its lowest refresh rate, 14Hz in TTY/1.

  4. When starting $ sway the next time, the refresh rate is stuck at 14Hz.

  • This cannot be reproduced if using config option adaptive_sync on (as the refresh rate gets updated from 14hz automatically to 144hz)

@emersion
Copy link
Member Author

Thanks for testing, however I'd like to reiterate that this PR isn't supposed to improve anything. This PR just replicates the existing Sway behavior with new APIs. IOW, it's a bug if this PR doesn't behave exactly as Sway master.

@Nogesma
Copy link

Nogesma commented Mar 15, 2024

I have been running wlroots master branch with the wlroots MR, and a modified version of this PR on top of sway master for about a week and it works great.

I am not using the max_render_time feature so I am just using a simple patch: https://gist.github.com/Nogesma/5ce59439fee0e8d7fc5454ce47d678f8

I haven't really noticed any major issues, and I can confirm that the bug with VRR not working when using direct scanout is fixed. I do not need to start sway/wlroots with any environment variable for it to work.

I still encounter the issue where moving the mouse cursor will break VRR, but I assume fixing it requires more work than just changing the frame scheduler.

If you still consider the change in behavior a bug, I can try to look into why the new frame scheduler fixes it, although I am not too familiar with the frame scheduling logic nor the drm part.
This should also probably be addressed in the wlroots MR as most of the frame scheduling code seems to be gone from sway due to the switch to using the scene graph api.

Specs

Distro: archlinux
Kernel: 6.8.1.arch1-1
Mesa: 1:24.0.3-1
Sway: master + patch
Wlroots: master + MR


GPU: AMD RX 6700 XT
Monitor: AW2725DF (Display port, 48-360Hz)

@YellowOnion
Copy link
Contributor

@Nogesma mouse movement doesn't actually break VFR, sway cursors are redrawn every time the mouse reports movement, which I assume, you with your 360hz monitor, you also have a gaming mouse doing 500hz/1000hz, if you set your mouse to 125hz polling it should drive the display at ~125hz, Sway treats all updates equally, I wrote a patch for wlr and sway to defer cursor updates to some max latency. if you want to try it.

patch-info: #6832 (comment)

@kennylevinsen
Copy link
Member

AMDGPU has some funny quirks where updating cursor position does not seem to drive the display faster and so cursor movements stutter at a lower refresh, but again note that this PR should have no functional changes.

Comment on lines +1087 to +1090
struct send_frame_done_data frame_done_data = {0};
clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when);
frame_done_data.msec_until_refresh = get_msec_until_refresh(event);
send_frame_done(output, &frame_done_data);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange for the compositor's handle_present() to also need to call get_msec_until_refresh() in order to determine the frame callback delay. get_msec_until_refresh() uses output->max_render_time, which is specific to the timed_frame_scheduler implemented here. For a generic frame scheduler, we don't seem to expose how long this delay should be, and assuming output->max_render_time seems wrong.

  • Should frame callback timers be set by the scheduler in its present function instead?
  • If it makes more sense to continue setting the callbacks on the compositor side, maybe this is better addressed in the frame scheduler MR?

I know this is on the back-burner, and things have changed a bit in the wlr_output events since the last rebase, so feel free to ignore if this no longer makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange for the compositor's handle_present() to also need to call get_msec_until_refresh() in order to determine the frame callback delay

Why do you think so? handle_present notifies us of when presentation occurred some time after the fact and is not representative of the actual time of presentation. We need to do a bit of math to figure out how much time is actually left at this point, using the reported time of presentation and estimated time from there till the next frame.

get_msec_until_refresh() uses output->max_render_time

Hmm? It doesn't in the PR as is. output->max_render_time is applied by timed_frame_scheduler itself to offset render from the value reported by get_msec_until_refresh.

For a generic frame scheduler, we don't seem to expose how long this delay should be

I believe the intent was to later implement a "fancy" frame scheduler that dynamically decides on a suitable delay, which would more or less deprecate max_render_time.

Copy link

@carbonXIII carbonXIII Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm? It doesn't in the PR as is. output->max_render_time is applied by timed_frame_scheduler itself to offset render from the value reported by get_msec_until_refresh.

Whoops, you're right, output->max_render_time is used by send_frame_done_iterator not in get_msec_until_refresh. That got mixed up at some point between this implementation, master, and my own patch 😅

Same point though, output->max_render_time is used outside of the frame scheduler.

I believe the intent was to later implement a "fancy" frame scheduler that dynamically decides on a suitable delay, which would more or less deprecate max_render_time.

We're on the same page here. There are technically 2 timings a frame scheduler implementation might want to configure separately. We're using next_refresh - max_render_time for both right now (ignoring view->max_render_time), but there is:

  • A) the timing of the frame event (which triggers rendering for the compositor)
  • B) the timing of the frame callbacks being called (which triggers rendering for clients)

(A) is already being handled entirely within this frame scheduler. My question is about how (B) should be handled by the frame scheduler, since in this implementation it's not.

Edit: Changed "delay from the present event" to "timing"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having messed around with implementing a frame scheduler for a bit, I now realize that it doesn't make sense for (B) to be implemented in wlr_frame_scheduler. Even though it is very similar to (A), it is probably more appropriate to handle per-surface, rather than per-output.

So I guess this PR doesn't need to address that, sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants