diff --git a/include/wlr/interfaces/wlr_frame_scheduler.h b/include/wlr/interfaces/wlr_frame_scheduler.h new file mode 100644 index 000000000..080a34228 --- /dev/null +++ b/include/wlr/interfaces/wlr_frame_scheduler.h @@ -0,0 +1,26 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_FRAME_SCHEDULER_H +#define WLR_INTERFACES_WLR_FRAME_SCHEDULER_H + +struct wlr_frame_scheduler; +struct wlr_output; + +struct wlr_frame_scheduler_impl { + /** + * Ensure that the scheduler->frame signal will be fired in the future. + */ + void (*schedule_frame)(struct wlr_frame_scheduler *scheduler); + void (*destroy)(struct wlr_frame_scheduler *scheduler); +}; + +void wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler, + const struct wlr_frame_scheduler_impl *impl, struct wlr_output *output); + +#endif diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h new file mode 100644 index 000000000..0372aefe7 --- /dev/null +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -0,0 +1,58 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FRAME_SCHEDULER_H +#define WLR_TYPES_WLR_FRAME_SCHEDULER_H + +#include +#include + +struct wlr_frame_scheduler_impl; + +struct wlr_frame_scheduler { + struct wlr_output *output; + + struct { + struct wl_signal frame; + } events; + + // Whether the render loop should be kept awake. True if wlr_frame_scheduler_schedule_frame() + // was called since the last frame event. + bool needs_frame; + + // private state + + const struct wlr_frame_scheduler_impl *impl; + + struct wl_listener backend_needs_frame; +}; + +/** + * The present scheduler maintains a render loop based on `wlr_output.events.present`. To wake the + * render loop, it emits the frame signal when the compositor's event loop is idle. + */ +struct wlr_frame_scheduler *wlr_present_scheduler_create(struct wlr_output *output); + +/** + * Creates an appropriate frame scheduler for the given output's backend capabilities. + */ +struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output); +/** + * Inform the scheduler that a frame signal is needed. The scheduler implementation will choose a + * good time to emit the signal. The signal is emitted only if this function has been called at + * least once since the last signal. + */ +void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler); +/** + * Emits a frame signal if `wlr_frame_scheduler_schedule_frame()` has been called since the last + * frame signal. + */ +void wlr_frame_scheduler_emit_frame(struct wlr_frame_scheduler *scheduler); +void wlr_frame_scheduler_destroy(struct wlr_frame_scheduler *scheduler); + +#endif diff --git a/types/meson.build b/types/meson.build index 26958f048..68800cacd 100644 --- a/types/meson.build +++ b/types/meson.build @@ -49,6 +49,7 @@ wlr_files += files( 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', 'wlr_ext_data_control_v1.c', + 'wlr_frame_scheduler.c', 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_image_copy_capture_v1.c', 'wlr_ext_workspace_v1.c', diff --git a/types/output/output.c b/types/output/output.c index 46da1f425..025d7d591 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -863,6 +863,7 @@ void wlr_output_schedule_frame(struct wlr_output *output) { // Make sure the compositor commits a new frame. This is necessary to make // clients which ask for frame callbacks without submitting a new buffer // work. + // TODO(rose): figure out why wlr_output_update_needs_frame(output); if (output->frame_pending || output->idle_frame != NULL) { diff --git a/types/wlr_frame_scheduler.c b/types/wlr_frame_scheduler.c new file mode 100644 index 000000000..82f7748f4 --- /dev/null +++ b/types/wlr_frame_scheduler.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +void wlr_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *scheduler) { + scheduler->needs_frame = true; + scheduler->impl->schedule_frame(scheduler); +} + +void wlr_frame_scheduler_destroy(struct wlr_frame_scheduler *scheduler) { + if (scheduler == NULL) { + return; + } + wl_list_remove(&scheduler->backend_needs_frame.link); + scheduler->impl->destroy(scheduler); +} + +void wlr_frame_scheduler_emit_frame(struct wlr_frame_scheduler *scheduler) { + if (!scheduler->needs_frame) { + return; + } + scheduler->needs_frame = false; + wl_signal_emit_mutable(&scheduler->events.frame, NULL); +} + +static void frame_scheduler_handle_needs_frame(struct wl_listener *listener, void *data) { + struct wlr_frame_scheduler *scheduler = wl_container_of(listener, scheduler, backend_needs_frame); + wlr_frame_scheduler_schedule_frame(scheduler); +} + +void wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler, + const struct wlr_frame_scheduler_impl *impl, struct wlr_output *output) { + assert(impl->schedule_frame); + assert(impl->destroy); + + *scheduler = (struct wlr_frame_scheduler){ + .impl = impl, + .output = output, + }; + wl_signal_init(&scheduler->events.frame); + scheduler->backend_needs_frame.notify = frame_scheduler_handle_needs_frame; + wl_signal_add(&output->events.needs_frame, &scheduler->backend_needs_frame); +} + +// This struct and its methods are a common base for frame schedulers that restart their render loop +// via an idle event source, which fires "soon", instead of using a more complex schedule. Deferring +// the frame to an idle event is a crude way of ensuring that work done after scheduling the frame +// gets picked up by the renderer, rather than rendering happening inside the schedule call and +// missing out on any immediately following updates. +struct idle_frame_scheduler { + struct wlr_frame_scheduler base; + struct wl_event_source *idle; + // Whether the render loop is already awake, i.e. whether frames from idle events should be + // inhibited. + bool frame_pending; +}; + +static void idle_frame_scheduler_emit_frame(struct idle_frame_scheduler *scheduler) { + scheduler->frame_pending = false; + wlr_frame_scheduler_emit_frame(&scheduler->base); +} + +static void idle_frame_scheduler_handle_idle(void *data) { + struct idle_frame_scheduler *scheduler = data; + if (!scheduler->frame_pending) { + idle_frame_scheduler_emit_frame(scheduler); + } + scheduler->idle = NULL; +} + +static void idle_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) { + struct idle_frame_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base); + if (scheduler->idle != NULL || scheduler->frame_pending) { + // Either we are already set up to restart the render loop or it is already running. + return; + } + + struct wl_event_loop *loop = scheduler->base.output->event_loop; + scheduler->idle = wl_event_loop_add_idle(loop, idle_frame_scheduler_handle_idle, scheduler); +} + +static void idle_frame_scheduler_set_frame_pending(struct idle_frame_scheduler *scheduler) { + scheduler->frame_pending = true; + if (scheduler->idle) { + wl_event_source_remove(scheduler->idle); + scheduler->idle = NULL; + } +} + +static void idle_frame_scheduler_finish(struct idle_frame_scheduler *scheduler) { + if (scheduler->idle) { + wl_event_source_remove(scheduler->idle); + } +} + +// The present scheduler builds on the idle_frame_scheduler's logic for restarting the render loop, +// and drives the render loop using `wlr_output.events.present`. +struct present_scheduler { + struct idle_frame_scheduler base; + struct wl_listener commit; + struct wl_listener present; +}; + +static void present_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct present_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base); + idle_frame_scheduler_finish(&scheduler->base); + wl_list_remove(&scheduler->commit.link); + wl_list_remove(&scheduler->present.link); + free(scheduler); +} + +static void present_scheduler_handle_commit(struct wl_listener *listener, void *data) { + struct present_scheduler *scheduler = wl_container_of(listener, scheduler, commit); + if (scheduler->base.base.output->enabled) { + idle_frame_scheduler_set_frame_pending(&scheduler->base); + } +} + +static void present_scheduler_handle_present(struct wl_listener *listener, void *data) { + struct present_scheduler *scheduler = wl_container_of(listener, scheduler, present); + struct wlr_output_event_present *present = data; + if (present->presented) { + idle_frame_scheduler_emit_frame(&scheduler->base); + } else { + scheduler->base.frame_pending = false; + } +} + +static const struct wlr_frame_scheduler_impl present_scheduler_impl = { + .schedule_frame = idle_frame_scheduler_schedule_frame, + .destroy = present_scheduler_destroy, +}; + +struct wlr_frame_scheduler *wlr_present_scheduler_create(struct wlr_output *output) { + struct present_scheduler *scheduler = calloc(1, sizeof(*scheduler)); + if (!scheduler) { + return NULL; + } + wlr_frame_scheduler_init(&scheduler->base.base, &present_scheduler_impl, output); + scheduler->commit.notify = present_scheduler_handle_commit; + wl_signal_add(&output->events.commit, &scheduler->commit); + scheduler->present.notify = present_scheduler_handle_present; + wl_signal_add(&output->events.present, &scheduler->present); + return &scheduler->base.base; +} + +struct wlr_frame_scheduler *wlr_frame_scheduler_autocreate(struct wlr_output *output) { + return wlr_present_scheduler_create(output); +}