diff options
Diffstat (limited to 'sway/desktop')
-rw-r--r-- | sway/desktop/output.c | 91 |
1 files changed, 78 insertions, 13 deletions
diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 05b5cd44..0f715c19 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -480,19 +480,13 @@ static bool scan_out_fullscreen_view(struct sway_output *output, return wlr_output_commit(wlr_output); } -static void damage_handle_frame(struct wl_listener *listener, void *data) { - struct sway_output *output = - wl_container_of(listener, output, damage_frame); - if (!output->enabled || !output->wlr_output->enabled) { - return; - } - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); +int output_repaint_timer_handler(void *data) { + struct sway_output *output = data; + output->wlr_output->block_idle_frame = false; struct sway_workspace *workspace = output->current.active_workspace; if (workspace == NULL) { - return; + return 0; } struct sway_container *fullscreen_con = root->fullscreen_global; @@ -515,7 +509,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) { last_scanned_out = scanned_out; if (scanned_out) { - goto frame_done; + return 0; } } @@ -524,17 +518,82 @@ static void damage_handle_frame(struct wl_listener *listener, void *data) { pixman_region32_init(&damage); if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &damage)) { - return; + return 0; } if (needs_frame) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + output_render(output, &now, &damage); } pixman_region32_fini(&damage); -frame_done: + return 0; +} + +static void damage_handle_frame(struct wl_listener *listener, void *data) { + struct sway_output *output = + wl_container_of(listener, output, damage_frame); + if (!output->enabled || !output->wlr_output->enabled) { + 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; + + if (output->max_render_time != 0) { + struct timespec now; + clockid_t presentation_clock + = wlr_backend_get_presentation_clock(server.backend); + clock_gettime(presentation_clock, &now); + + 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; + } + + // 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 disasterous 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; + } + } + + 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); + } else { + output->wlr_output->block_idle_frame = true; + wl_event_source_timer_update(output->repaint_timer, delay); + } + // Send frame done to all visible surfaces + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); send_frame_done(output, &now); } @@ -768,6 +827,9 @@ static void handle_present(struct wl_listener *listener, void *data) { return; } + output->last_presentation = *output_event->when; + output->refresh_nsec = output_event->refresh; + struct wlr_presentation_event event = { .output = output->wlr_output, .tv_sec = (uint64_t)output_event->when->tv_sec, @@ -806,6 +868,9 @@ void handle_new_output(struct wl_listener *listener, void *data) { wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); output->damage_destroy.notify = damage_handle_destroy; + output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, + output_repaint_timer_handler, output); + struct output_config *oc = find_output_config(output); if (!oc || oc->enabled) { output_enable(output, oc); |