diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h index 16579c0a5..f7b0ddf94 100644 --- a/include/wlr/types/wlr_frame_scheduler.h +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -10,9 +10,11 @@ #define WLR_TYPES_WLR_FRAME_SCHEDULER_H #include +#include #include struct wlr_frame_scheduler_impl; +struct wlr_render_timer; struct wlr_frame_scheduler { struct wlr_output *output; @@ -65,4 +67,23 @@ void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler); void wlr_frame_scheduler_emit_frame(struct wlr_frame_scheduler *scheduler); void wlr_frame_scheduler_destroy(struct wlr_frame_scheduler *scheduler); +/** + * The predictive scheduler maintains a render loop based on `wlr_output.events.present`, and + * schedules frame signals to arrive just before the estimated render deadline. It learns from + * historic render times provided via wlr_frame_scheduler_inform_render(). + */ +struct wlr_frame_scheduler *wlr_predictive_frame_scheduler_create(struct wlr_output *output); +/** + * Provide render timing feedback to the scheduler. Must be called after each output commit. + * + * pre_render_duration_ns is the wall time from the frame signal to the start of the render pass. + * + * render_timer is the GPU render timer for this frame. May be NULL if no rendering was performed + * (e.g. direct scanout), in which case GPU render time is assumed to be zero. + * + * This is a no-op for non-predictive schedulers. + */ +void wlr_frame_scheduler_inform_render(struct wlr_frame_scheduler *scheduler, + int64_t pre_render_duration_ns, struct wlr_render_timer *render_timer); + #endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 9d54c0057..86f1f7934 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -268,12 +268,9 @@ struct wlr_scene_output { uint64_t in_point; struct wlr_drm_syncobj_timeline *out_timeline; uint64_t out_point; - } WLR_PRIVATE; -}; -struct wlr_scene_timer { - int64_t pre_render_duration; - struct wlr_render_timer *render_timer; + struct wlr_render_timer *render_timer; + } WLR_PRIVATE; }; /** A layer shell scene helper */ @@ -601,8 +598,6 @@ void wlr_scene_output_set_frame_scheduler(struct wlr_scene_output *scene_output, struct wlr_frame_scheduler *scheduler); struct wlr_scene_output_state_options { - struct wlr_scene_timer *timer; - /** * Color transform to apply before the output's color transform. Cannot be * used when the output has a non-NULL image description set. @@ -629,15 +624,6 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_output_state *state, const struct wlr_scene_output_state_options *options); -/** - * Retrieve the duration in nanoseconds between the last wlr_scene_output_commit() call and the end - * of its operations, including those on the GPU that may have finished after the call returned. - * - * Returns -1 if the duration is unavailable. - */ -int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer); -void wlr_scene_timer_finish(struct wlr_scene_timer *timer); - /** * Call wlr_surface_send_frame_done() on all surfaces in the scene rendered by * wlr_scene_output_commit() for which wlr_scene_surface.primary_output diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index e65314ccc..ffffbb7cc 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -329,6 +329,9 @@ int wlr_render_timer_get_duration_ns(struct wlr_render_timer *timer) { } void wlr_render_timer_destroy(struct wlr_render_timer *timer) { + if (!timer) { + return; + } if (!timer->impl->destroy) { return; } diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index bf175f661..59c1500e6 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1781,6 +1781,8 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, scene_output_update_geometry(scene_output, false); + scene_output->render_timer = wlr_render_timer_create(output->renderer); + return scene_output; } @@ -1818,6 +1820,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { } wlr_addon_finish(&scene_output->addon); + wlr_render_timer_destroy(scene_output->render_timer); wlr_frame_scheduler_destroy(scene_output->frame_scheduler); wlr_damage_ring_finish(&scene_output->damage_ring); pixman_region32_fini(&scene_output->pending_commit_damage); @@ -2279,13 +2282,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (!options) { options = &default_options; } - struct wlr_scene_timer *timer = options->timer; struct timespec start_time; - if (timer) { - clock_gettime(CLOCK_MONOTONIC, &start_time); - wlr_scene_timer_finish(timer); - *timer = (struct wlr_scene_timer){0}; - } + clock_gettime(CLOCK_MONOTONIC, &start_time); if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { // if the state is being disabled, do nothing. @@ -2432,12 +2430,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (scanout) { scene_output_state_attempt_gamma(scene_output, state); - if (timer) { - struct timespec end_time, duration; - clock_gettime(CLOCK_MONOTONIC, &end_time); - timespec_sub(&duration, &end_time, &start_time); - timer->pre_render_duration = timespec_to_nsec(&duration); - } + struct timespec end_time, duration; + clock_gettime(CLOCK_MONOTONIC, &end_time); + timespec_sub(&duration, &end_time, &start_time); + wlr_frame_scheduler_inform_render(scene_output->frame_scheduler, + timespec_to_nsec(&duration), NULL); return true; } @@ -2457,13 +2454,12 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, assert(buffer->width == resolution_width && buffer->height == resolution_height); - if (timer) { - timer->render_timer = wlr_render_timer_create(output->renderer); - + int64_t pre_render_duration_ns; + { struct timespec end_time, duration; clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_sub(&duration, &end_time, &start_time); - timer->pre_render_duration = timespec_to_nsec(&duration); + pre_render_duration_ns = timespec_to_nsec(&duration); } if ((render_gamma_lut @@ -2482,7 +2478,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ - .timer = timer ? timer->render_timer : NULL, + .timer = scene_output->render_timer, .color_transform = scene_output->combined_color_transform, .signal_timeline = scene_output->in_timeline, .signal_point = scene_output->in_point, @@ -2606,24 +2602,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, scene_output_state_attempt_gamma(scene_output, state); } + wlr_frame_scheduler_inform_render(scene_output->frame_scheduler, + pre_render_duration_ns, scene_output->render_timer); return true; } -int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) { - int64_t pre_render = timer->pre_render_duration; - if (!timer->render_timer) { - return pre_render; - } - int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer); - return render != -1 ? pre_render + render : -1; -} - -void wlr_scene_timer_finish(struct wlr_scene_timer *timer) { - if (timer->render_timer) { - wlr_render_timer_destroy(timer->render_timer); - } -} - static void scene_node_send_frame_done(struct wlr_scene_node *node, struct wlr_scene_output *scene_output, struct timespec *now) { if (!node->enabled) { diff --git a/types/wlr_frame_scheduler.c b/types/wlr_frame_scheduler.c index f7ffa5ddb..04db2a6e0 100644 --- a/types/wlr_frame_scheduler.c +++ b/types/wlr_frame_scheduler.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -283,6 +285,209 @@ struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *out return &scheduler->base.base; } +// The predictive scheduler schedules frame signals based on historic render times, aiming to +// start rendering as late as possible while still meeting the next vblank deadline. +struct predictive_scheduler { + struct wlr_frame_scheduler base; + struct wl_event_source *idle; + struct wl_event_source *timer; + struct wl_listener commit; + struct wl_listener present; + + bool frame_pending; + + int64_t estimated_frame_time_ns; + bool has_estimate; + + int64_t pending_pre_render_ns; + struct wlr_render_timer *pending_render_timer; + bool has_pending_feedback; +}; + +static const struct wlr_frame_scheduler_impl predictive_scheduler_impl; + +static int64_t compute_margin_ns(int refresh_ns) { + int64_t margin = refresh_ns / 20; + if (margin < 1500000) { + margin = 1500000; + } + return margin; +} + +static void update_estimate(struct predictive_scheduler *scheduler, int64_t sample_ns) { + if (!scheduler->has_estimate) { + scheduler->estimated_frame_time_ns = sample_ns; + scheduler->has_estimate = true; + return; + } + + if (sample_ns > scheduler->estimated_frame_time_ns) { + // React quickly to spikes + scheduler->estimated_frame_time_ns = + (9 * sample_ns + scheduler->estimated_frame_time_ns) / 10; + } else { + // Decay slowly + scheduler->estimated_frame_time_ns = + (sample_ns + 9 * scheduler->estimated_frame_time_ns) / 10; + } +} + +static void predictive_scheduler_consume_feedback(struct predictive_scheduler *scheduler) { + if (!scheduler->has_pending_feedback) { + return; + } + scheduler->has_pending_feedback = false; + + int64_t gpu_ns = 0; + if (scheduler->pending_render_timer != NULL) { + int duration = wlr_render_timer_get_duration_ns(scheduler->pending_render_timer); + if (duration > 0) { + gpu_ns = duration; + } + } + + update_estimate(scheduler, scheduler->pending_pre_render_ns + gpu_ns); +} + +static void predictive_scheduler_handle_idle(void *data) { + struct predictive_scheduler *scheduler = data; + scheduler->idle = NULL; + scheduler->frame_pending = false; + wlr_frame_scheduler_emit_frame(&scheduler->base); +} + +static int predictive_scheduler_handle_timer(void *data) { + struct predictive_scheduler *scheduler = data; + scheduler->frame_pending = false; + wlr_frame_scheduler_emit_frame(&scheduler->base); + return 0; +} + +static void predictive_scheduler_set_timer(struct predictive_scheduler *scheduler, int ms) { + if (scheduler->idle != NULL) { + wl_event_source_remove(scheduler->idle); + scheduler->idle = NULL; + } + wl_event_source_timer_update(scheduler->timer, ms); +} + +static void predictive_scheduler_set_idle(struct predictive_scheduler *scheduler) { + wl_event_source_timer_update(scheduler->timer, 0); + if (!scheduler->idle) { + scheduler->idle = wl_event_loop_add_idle( + scheduler->base.output->event_loop, + predictive_scheduler_handle_idle, scheduler); + } +} + +static void predictive_scheduler_cancel(struct predictive_scheduler *scheduler) { + if (scheduler->idle != NULL) { + wl_event_source_remove(scheduler->idle); + scheduler->idle = NULL; + } + wl_event_source_timer_update(scheduler->timer, 0); +} + +static void predictive_scheduler_handle_commit(struct wl_listener *listener, void *data) { + struct predictive_scheduler *scheduler = wl_container_of(listener, scheduler, commit); + if (scheduler->base.output->enabled) { + scheduler->frame_pending = true; + predictive_scheduler_cancel(scheduler); + } +} + +static void predictive_scheduler_handle_present(struct wl_listener *listener, void *data) { + struct predictive_scheduler *scheduler = wl_container_of(listener, scheduler, present); + struct wlr_output_event_present *event = data; + + if (!event->presented) { + scheduler->frame_pending = false; + return; + } + + predictive_scheduler_consume_feedback(scheduler); + + if (!scheduler->has_estimate || event->refresh == 0) { + predictive_scheduler_set_idle(scheduler); + return; + } + + int64_t refresh_ns = event->refresh; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + int64_t present_ns = (int64_t)event->when.tv_sec * 1000000000 + event->when.tv_nsec; + int64_t now_ns = (int64_t)now.tv_sec * 1000000000 + now.tv_nsec; + int64_t next_vblank_ns = present_ns + refresh_ns; + + int64_t margin = compute_margin_ns(refresh_ns); + int64_t deadline_ns = next_vblank_ns - scheduler->estimated_frame_time_ns - margin; + int64_t delay_ns = deadline_ns - now_ns; + + if (delay_ns < 1000000) { + predictive_scheduler_set_idle(scheduler); + } else { + int delay_ms = (int)(delay_ns / 1000000); + predictive_scheduler_set_timer(scheduler, delay_ms); + } +} + +static void predictive_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) { + struct predictive_scheduler *scheduler = + wl_container_of(wlr_scheduler, scheduler, base); + if (scheduler->idle != NULL || scheduler->frame_pending) { + return; + } + predictive_scheduler_set_idle(scheduler); +} + +static void predictive_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct predictive_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); + wl_list_remove(&scheduler->commit.link); + wl_list_remove(&scheduler->present.link); + free(scheduler); +} + +static const struct wlr_frame_scheduler_impl predictive_scheduler_impl = { + .schedule_frame = predictive_scheduler_schedule_frame, + .destroy = predictive_scheduler_destroy, +}; + +struct wlr_frame_scheduler *wlr_predictive_frame_scheduler_create(struct wlr_output *output) { + struct predictive_scheduler *scheduler = calloc(1, sizeof(*scheduler)); + if (!scheduler) { + return NULL; + } + wlr_frame_scheduler_init(&scheduler->base, &predictive_scheduler_impl, output); + + scheduler->timer = wl_event_loop_add_timer(output->event_loop, + predictive_scheduler_handle_timer, scheduler); + + scheduler->commit.notify = predictive_scheduler_handle_commit; + wl_signal_add(&output->events.commit, &scheduler->commit); + scheduler->present.notify = predictive_scheduler_handle_present; + wl_signal_add(&output->events.present, &scheduler->present); + return &scheduler->base; +} + +void wlr_frame_scheduler_inform_render(struct wlr_frame_scheduler *scheduler, + int64_t pre_render_duration_ns, struct wlr_render_timer *render_timer) { + if (scheduler->impl != &predictive_scheduler_impl) { + return; + } + struct predictive_scheduler *predictive = + wl_container_of(scheduler, predictive, base); + + predictive->pending_pre_render_ns = pre_render_duration_ns; + predictive->pending_render_timer = render_timer; + predictive->has_pending_feedback = true; +} + struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output) { if (wlr_output_is_wl(output) && !wlr_wl_backend_has_presentation_time(output->backend)) { wlr_log(WLR_INFO, "wp_presentation not available, falling back to frame callbacks");