From 31c891dddbf4eed4f43b8db4d1fc0874b20e2087 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 29 Nov 2023 20:54:50 +0100 Subject: [PATCH] Implement output max_render_time via wlr_frame_scheduler --- include/sway/output.h | 21 +++- sway/config/output.c | 2 +- sway/desktop/output.c | 229 +++++++++++++++++++++++++++++++----------- sway/tree/output.c | 2 - 4 files changed, 188 insertions(+), 66 deletions(-) diff --git a/include/sway/output.h b/include/sway/output.h index 5da57f2e..6c6d0fa3 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "config.h" #include "sway/tree/node.h" @@ -52,10 +53,7 @@ struct sway_output { struct wl_signal disable; } events; - struct timespec last_presentation; - uint32_t refresh_nsec; int max_render_time; // In milliseconds - struct wl_event_source *repaint_timer; bool gamma_lut_changed; }; @@ -73,6 +71,21 @@ struct render_context { struct wlr_render_pass *pass; }; +struct sway_timer_frame_scheduler { + struct wlr_frame_scheduler base; + + int max_render_time; + + struct wl_event_source *idle; + struct wl_event_source *timer; + + bool frame_pending; + bool needs_frame; + + struct wl_listener commit; + struct wl_listener present; +}; + struct sway_output *output_create(struct wlr_output *wlr_output); void output_destroy(struct sway_output *output); @@ -119,6 +132,8 @@ void output_enable(struct sway_output *output); void output_disable(struct sway_output *output); +void output_set_max_render_time(struct sway_output *output, int max_render_time); + bool output_has_opaque_overlay_layer_surface(struct sway_output *output); struct sway_workspace *output_get_active_workspace(struct sway_output *output); diff --git a/sway/config/output.c b/sway/config/output.c index 3316085a..31244ae4 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -584,7 +584,7 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { if (oc && oc->max_render_time >= 0) { sway_log(SWAY_DEBUG, "Set %s max render time to %d", oc->name, oc->max_render_time); - output->max_render_time = oc->max_render_time; + output_set_max_render_time(output, oc->max_render_time); } // Reconfigure all devices, since input config may have been applied before diff --git a/sway/desktop/output.c b/sway/desktop/output.c index f78f5393..3c3df625 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -6,10 +6,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -558,16 +558,151 @@ static void get_frame_damage(struct sway_output *output, } } -static int output_repaint_timer_handler(void *data) { - struct sway_output *output = data; +static int get_msec_until_refresh(const struct wlr_output_event_present *event) { + // Compute predicted milliseconds until the next refresh. It's used for + // delaying both output rendering and surface frame callbacks. + int msec_until_refresh = 0; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + const long NSEC_IN_SECONDS = 1000000000; + struct timespec predicted_refresh = *event->when; + predicted_refresh.tv_nsec += event->refresh % NSEC_IN_SECONDS; + predicted_refresh.tv_sec += event->refresh / NSEC_IN_SECONDS; + if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) { + predicted_refresh.tv_sec += 1; + predicted_refresh.tv_nsec -= NSEC_IN_SECONDS; + } + + // If the predicted refresh time is before the current time then + // there's no point in delaying. + // + // We only check tv_sec because if the predicted refresh time is less + // than a second before the current time, then msec_until_refresh will + // end up slightly below zero, which will effectively disable the delay + // without potential disastrous negative overflows that could occur if + // tv_sec was not checked. + if (predicted_refresh.tv_sec >= now.tv_sec) { + long nsec_until_refresh + = (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS + + (predicted_refresh.tv_nsec - now.tv_nsec); + + // We want msec_until_refresh to be conservative, that is, floored. + // If we have 7.9 msec until refresh, we better compute the delay + // as if we had only 7 msec, so that we don't accidentally delay + // more than necessary and miss a frame. + msec_until_refresh = nsec_until_refresh / 1000000; + } + + return msec_until_refresh; +} + +static void timed_frame_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct sway_timer_frame_scheduler *scheduler = + wl_container_of(wlr_scheduler, scheduler, base); + if (scheduler->idle != NULL) { + wl_event_source_remove(scheduler->idle); + } + wl_event_source_remove(scheduler->timer); + free(scheduler); +} + +static void timed_frame_scheduler_trigger_frame( + struct sway_timer_frame_scheduler *scheduler) { + if (!scheduler->needs_frame) { + return; + } + scheduler->needs_frame = false; + wl_signal_emit_mutable(&scheduler->base.events.frame, NULL); +} + +static void timed_frame_scheduler_handle_idle(void *data) { + struct sway_timer_frame_scheduler *scheduler = data; + scheduler->idle = NULL; + timed_frame_scheduler_trigger_frame(scheduler); +} + +static void timed_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) { + struct sway_timer_frame_scheduler *scheduler = + wl_container_of(wlr_scheduler, scheduler, base); + scheduler->needs_frame = true; + if (scheduler->idle != NULL || scheduler->frame_pending) { + return; + } + scheduler->idle = wl_event_loop_add_idle(scheduler->base.output->event_loop, + timed_frame_scheduler_handle_idle, scheduler); +} + +static const struct wlr_frame_scheduler_impl timed_frame_scheduler_impl = { + .destroy = timed_frame_scheduler_destroy, + .schedule_frame = timed_frame_scheduler_schedule_frame, +}; + +static int timed_frame_scheduler_handle_timer(void *data) { + struct sway_timer_frame_scheduler *scheduler = data; + scheduler->frame_pending = false; + timed_frame_scheduler_trigger_frame(scheduler); + return 0; +} + +static void timed_frame_scheduler_handle_present(struct wl_listener *listener, void *data) { + struct sway_timer_frame_scheduler *scheduler = + wl_container_of(listener, scheduler, present); + const struct wlr_output_event_present *event = data; + + if (!event->presented) { + return; + } + + assert(!scheduler->frame_pending); + + if (scheduler->idle != NULL) { + wl_event_source_remove(scheduler->idle); + scheduler->idle = NULL; + } + + int msec_until_refresh = get_msec_until_refresh(event); + int delay = msec_until_refresh - scheduler->max_render_time; + + // If the delay is less than 1 millisecond (which is the least we can wait) + // then just render right away. + if (delay < 1) { + wl_signal_emit_mutable(&scheduler->base.events.frame, NULL); + } else { + scheduler->frame_pending = true; + wl_event_source_timer_update(scheduler->timer, delay); + } +} + +static struct wlr_frame_scheduler *timed_frame_scheduler_create(struct wlr_output *output, + int max_render_time) { + struct sway_timer_frame_scheduler *scheduler = calloc(1, sizeof(*scheduler)); + if (scheduler == NULL) { + return NULL; + } + wlr_frame_scheduler_init(&scheduler->base, &timed_frame_scheduler_impl, output); + + scheduler->max_render_time = max_render_time; + + scheduler->timer = wl_event_loop_add_timer(server.wl_event_loop, + timed_frame_scheduler_handle_timer, scheduler); + + scheduler->present.notify = timed_frame_scheduler_handle_present; + wl_signal_add(&output->events.present, &scheduler->present); + + return &scheduler->base; +} + +static void output_redraw(struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; if (wlr_output == NULL) { - return 0; + return; } struct sway_workspace *workspace = output->current.active_workspace; if (workspace == NULL) { - return 0; + return; } struct sway_container *fullscreen_con = root->fullscreen_global; @@ -675,7 +810,6 @@ static int output_repaint_timer_handler(void *data) { out: wlr_output_state_finish(&pending); - return 0; } static void handle_damage(struct wl_listener *listener, void *user_data) { @@ -694,59 +828,33 @@ static void handle_frame(struct wl_listener *listener, void *user_data) { return; } - // Compute predicted milliseconds until the next refresh. It's used for - // delaying both output rendering and surface frame callbacks. - int msec_until_refresh = 0; + output_redraw(output); +} - if (output->max_render_time != 0) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); +static void output_set_frame_scheduler(struct sway_output *output, + struct wlr_frame_scheduler *scheduler) { + wl_list_remove(&output->frame.link); - const long NSEC_IN_SECONDS = 1000000000; - struct timespec predicted_refresh = output->last_presentation; - predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS; - predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS; - if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) { - predicted_refresh.tv_sec += 1; - predicted_refresh.tv_nsec -= NSEC_IN_SECONDS; - } + wlr_frame_scheduler_destroy(output->frame_scheduler); + output->frame_scheduler = scheduler; - // If the predicted refresh time is before the current time then - // there's no point in delaying. - // - // We only check tv_sec because if the predicted refresh time is less - // than a second before the current time, then msec_until_refresh will - // end up slightly below zero, which will effectively disable the delay - // without potential disastrous negative overflows that could occur if - // tv_sec was not checked. - if (predicted_refresh.tv_sec >= now.tv_sec) { - long nsec_until_refresh - = (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS - + (predicted_refresh.tv_nsec - now.tv_nsec); + output->frame.notify = handle_frame; + wl_signal_add(&output->frame_scheduler->events.frame, &output->frame); +} - // We want msec_until_refresh to be conservative, that is, floored. - // If we have 7.9 msec until refresh, we better compute the delay - // as if we had only 7 msec, so that we don't accidentally delay - // more than necessary and miss a frame. - msec_until_refresh = nsec_until_refresh / 1000000; - } - } - - int delay = msec_until_refresh - output->max_render_time; - - // If the delay is less than 1 millisecond (which is the least we can wait) - // then just render right away. - if (delay < 1) { - output_repaint_timer_handler(output); +void output_set_max_render_time(struct sway_output *output, int max_render_time) { + struct wlr_frame_scheduler *scheduler; + if (max_render_time != 0) { + scheduler = timed_frame_scheduler_create(output->wlr_output, max_render_time); } else { - wl_event_source_timer_update(output->repaint_timer, delay); + scheduler = wlr_frame_scheduler_autocreate(output->wlr_output); + } + if (scheduler == NULL) { + sway_log(SWAY_ERROR, "Failed to create output frame scheduler"); + return; } - // Send frame done to all visible surfaces - struct send_frame_done_data data = {0}; - clock_gettime(CLOCK_MONOTONIC, &data.when); - data.msec_until_refresh = msec_until_refresh; - send_frame_done(output, &data); + output_set_frame_scheduler(output, scheduler); } void output_damage_whole(struct sway_output *output) { @@ -969,14 +1077,17 @@ static void handle_commit(struct wl_listener *listener, void *data) { static void handle_present(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, present); - struct wlr_output_event_present *output_event = data; + struct wlr_output_event_present *event = data; - if (!output->enabled || !output_event->presented) { + if (!output->enabled || !event->presented) { return; } - output->last_presentation = *output_event->when; - output->refresh_nsec = output_event->refresh; + // Send frame done to all visible surfaces + 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); } static void handle_request_state(struct wl_listener *listener, void *data) { @@ -1041,13 +1152,11 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->present.notify = handle_present; wl_signal_add(&wlr_output->events.damage, &output->damage); output->damage.notify = handle_damage; - wl_signal_add(&output->frame_scheduler->events.frame, &output->frame); - output->frame.notify = handle_frame; wl_signal_add(&wlr_output->events.request_state, &output->request_state); output->request_state.notify = handle_request_state; - output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, - output_repaint_timer_handler, output); + wl_list_init(&output->frame.link); + output_set_max_render_time(output, 0); struct output_config *oc = find_output_config(output); apply_output_config(oc, output); diff --git a/sway/tree/output.c b/sway/tree/output.c index 7ddff164..69e60b37 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -95,7 +95,6 @@ struct sway_output *output_create(struct wlr_output *wlr_output) { wlr_output->data = output; output->detected_subpixel = wlr_output->subpixel; output->scale_filter = SCALE_FILTER_NEAREST; - output->frame_scheduler = wlr_frame_scheduler_autocreate(wlr_output); wl_signal_init(&output->events.disable); @@ -242,7 +241,6 @@ void output_destroy(struct sway_output *output) { } list_free(output->workspaces); list_free(output->current.workspaces); - wl_event_source_remove(output->repaint_timer); free(output); }