diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h index 8bfa6d580..16579c0a5 100644 --- a/include/wlr/types/wlr_frame_scheduler.h +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -42,6 +42,11 @@ struct wlr_frame_scheduler *wlr_present_scheduler_create(struct wlr_output *outp * render loop, it emits the frame signal when the compositor's event loop is idle. */ struct wlr_frame_scheduler *wlr_wl_scheduler_create(struct wlr_output *output); +/** + * The interval scheduler maintains a render loop based on a timer. To wake the render loop, it + * emits the frame signal when the compositor's event loop is idle. + */ +struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *output); /** * Creates an appropriate frame scheduler for the given output's backend capabilities. diff --git a/types/wlr_frame_scheduler.c b/types/wlr_frame_scheduler.c index a76a591d1..f7ffa5ddb 100644 --- a/types/wlr_frame_scheduler.c +++ b/types/wlr_frame_scheduler.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -215,11 +216,82 @@ static struct wlr_frame_scheduler *wl_scheduler_create(struct wlr_output *output return &scheduler->base.base; } +#define DEFAULT_REFRESH (60 * 1000) // 60 Hz in mHz + +struct interval_scheduler { + struct idle_frame_scheduler base; + struct wl_event_source *frame_timer; + struct wl_listener commit; + + int32_t frame_delay; +}; + +static void interval_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct interval_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base); + idle_frame_scheduler_finish(&scheduler->base); + wl_event_source_remove(scheduler->frame_timer); + wl_list_remove(&scheduler->commit.link); + free(scheduler); +} + +static void interval_scheduler_handle_commit(struct wl_listener *listener, void *data) { + struct interval_scheduler *scheduler = wl_container_of(listener, scheduler, commit); + struct wlr_output_event_commit *commit = data; + struct wlr_output *output = commit->output; + if (commit->state->committed & WLR_OUTPUT_STATE_MODE) { + int32_t refresh = output->refresh ? output->refresh : DEFAULT_REFRESH; + scheduler->frame_delay = 1000 * 1000 / refresh; + } + + if (output->enabled) { + assert(scheduler->frame_delay != 0); + wl_event_source_timer_update(scheduler->frame_timer, scheduler->frame_delay); + idle_frame_scheduler_set_frame_pending(&scheduler->base); + } +} + +static int interval_scheduler_handle_timer(void *data) { + struct interval_scheduler *scheduler = data; + idle_frame_scheduler_emit_frame(&scheduler->base); + return 0; +} + +static const struct wlr_frame_scheduler_impl interval_scheduler_impl = { + .schedule_frame = idle_frame_scheduler_schedule_frame, + .destroy = interval_scheduler_destroy, +}; + +struct wlr_frame_scheduler *wlr_interval_scheduler_create(struct wlr_output *output) { + if (!wlr_output_is_headless(output)) { + return NULL; + } + + struct interval_scheduler *scheduler = calloc(1, sizeof(*scheduler)); + if (!scheduler) { + return NULL; + } + wlr_frame_scheduler_init(&scheduler->base.base, &interval_scheduler_impl, output); + + scheduler->frame_delay = 1000 * 1000 / DEFAULT_REFRESH; + + scheduler->frame_timer = wl_event_loop_add_timer(output->event_loop, + interval_scheduler_handle_timer, scheduler); + + scheduler->commit.notify = interval_scheduler_handle_commit; + wl_signal_add(&output->events.commit, &scheduler->commit); + + return &scheduler->base.base; +} + 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"); return wl_scheduler_create(output); } + if (wlr_output_is_headless(output)) { + return wlr_interval_scheduler_create(output); + } + return wlr_present_scheduler_create(output); }