From 9b3588e97c753800e31011fdd06f6991415bd0d5 Mon Sep 17 00:00:00 2001 From: Rose Hudson Date: Thu, 10 Aug 2023 12:29:18 +0100 Subject: [PATCH] add wl_surface.frame based frame scheduler --- backend/wayland/backend.c | 5 ++ include/wlr/backend/wayland.h | 5 ++ include/wlr/types/wlr_frame_scheduler.h | 5 ++ types/wlr_frame_scheduler.c | 72 ++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index ba67b2bfe..9395b14ff 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -563,6 +563,11 @@ bool wlr_backend_is_wl(struct wlr_backend *b) { return b->impl == &backend_impl; } +bool wlr_wl_backend_has_presentation_time(struct wlr_backend *wlr_backend) { + struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend); + return backend->presentation != NULL; +} + static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_wl_backend *wl = wl_container_of(listener, wl, local_display_destroy); diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h index a30bac257..5a2d255de 100644 --- a/include/wlr/backend/wayland.h +++ b/include/wlr/backend/wayland.h @@ -47,6 +47,11 @@ struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend */ bool wlr_backend_is_wl(struct wlr_backend *backend); +/** + * Check whether the Wayland server advertises the wp_presentation protocol. + */ +bool wlr_wl_backend_has_presentation_time(struct wlr_backend *backend); + /** * Check whether the provided input device is a Wayland input device. */ diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h index eaec9f31f..4305209ac 100644 --- a/include/wlr/types/wlr_frame_scheduler.h +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -26,6 +26,11 @@ struct wlr_frame_scheduler { * the render loop, it emits the frame signal when the compositor's event loop is idle. */ struct wlr_frame_scheduler *wlr_present_idle_scheduler_create(struct wlr_output *output); +/* + * The Wayland-idle scheduler maintains a render loop based on wl_surface.frame callbacks. To wake + * the render loop, it emits the frame signal when the compositor's event loop is idle. + */ +struct wlr_frame_scheduler *wlr_wl_idle_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 dd44fe169..e0fc9ef62 100644 --- a/types/wlr_frame_scheduler.c +++ b/types/wlr_frame_scheduler.c @@ -1,9 +1,13 @@ +#include #include +#include #include #include +#include #include #include #include +#include void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler) { scheduler->impl->schedule_frame(scheduler); @@ -142,7 +146,73 @@ struct wlr_frame_scheduler *wlr_present_idle_scheduler_create(struct wlr_output return &scheduler->base.base; } +// This scheduler builds on idle_frame_scheduler and uses Wayland's frame callbacks for driving the +// render loop. +struct wl_idle_scheduler { + struct idle_frame_scheduler base; + struct wl_callback *frame; + struct wl_listener precommit; +}; + +static void wl_idle_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct wl_idle_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base); + idle_frame_scheduler_finish(&scheduler->base); + wl_callback_destroy(scheduler->frame); + free(scheduler); +} + +static void wl_idle_scheduler_handle_frame(void *data, struct wl_callback *cb, uint32_t time) { + struct wl_idle_scheduler *scheduler = data; + assert(scheduler->frame == cb); + scheduler->base.frame_pending = false; + idle_frame_scheduler_emit_frame(&scheduler->base); +} + +struct wl_callback_listener wl_idle_scheduler_frame_listener = { + .done = wl_idle_scheduler_handle_frame, +}; + +static void wl_idle_scheduler_handle_precommit(struct wl_listener *listener, void *data) { + struct wl_idle_scheduler *scheduler = wl_container_of(listener, scheduler, precommit); + struct wlr_output_event_precommit *precommit = data; + if (!(precommit->state->committed & (WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_LAYERS))) { + return; + } + idle_frame_scheduler_set_frame_pending(&scheduler->base); + if (scheduler->frame != NULL) { + wl_callback_destroy(scheduler->frame); + } + struct wl_surface *surface = wlr_wl_output_get_surface(scheduler->base.base.output); + scheduler->frame = wl_surface_frame(surface); + wl_callback_add_listener(scheduler->frame, &wl_idle_scheduler_frame_listener, scheduler); +} + +struct wlr_frame_scheduler_impl wl_idle_scheduler_impl = { + .schedule_frame = idle_frame_scheduler_schedule_frame, + .destroy = wl_idle_scheduler_destroy, +}; + +static struct wlr_frame_scheduler *wl_idle_scheduler_create(struct wlr_output *output) { + if (!wlr_output_is_wl(output)) { + return NULL; + } + + struct wl_idle_scheduler *scheduler = calloc(1, sizeof(struct wl_idle_scheduler)); + if (!scheduler) { + return NULL; + } + frame_scheduler_init(&scheduler->base.base, &wl_idle_scheduler_impl, output); + scheduler->precommit.notify = wl_idle_scheduler_handle_precommit; + wl_signal_add(&output->events.precommit, &scheduler->precommit); + + return &scheduler->base.base; +} + struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output) { - // TODO: check for presentation-time in wayland backend and work with frame cbs if absent + 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_idle_scheduler_create(output); + } + return wlr_present_idle_scheduler_create(output); }