diff --git a/backend/drm/drm.c b/backend/drm/drm.c index d2f75f71f..21bae02a2 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -2081,10 +2081,6 @@ static void handle_page_flip(int fd, unsigned seq, .flags = present_flags, }; wlr_output_send_present(&conn->output, &present_event); - - if (drm->session->active) { - wlr_output_send_frame(&conn->output); - } } int handle_drm_event(int fd, uint32_t mask, void *data) { diff --git a/backend/headless/output.c b/backend/headless/output.c index 0464e0d1b..d6206ac47 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -22,15 +22,6 @@ static struct wlr_headless_output *headless_output_from_output( return output; } -static void output_update_refresh(struct wlr_headless_output *output, - int32_t refresh) { - if (refresh <= 0) { - refresh = HEADLESS_DEFAULT_REFRESH; - } - - output->frame_delay = 1000000 / refresh; -} - static bool output_test(struct wlr_output *wlr_output, const struct wlr_output_state *state) { uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; @@ -55,25 +46,16 @@ static bool output_test(struct wlr_output *wlr_output, static bool output_commit(struct wlr_output *wlr_output, const struct wlr_output_state *state) { - struct wlr_headless_output *output = - headless_output_from_output(wlr_output); - if (!output_test(wlr_output, state)) { return false; } - if (state->committed & WLR_OUTPUT_STATE_MODE) { - output_update_refresh(output, state->custom_mode.refresh); - } - if (output_pending_enabled(wlr_output, state)) { struct wlr_output_event_present present_event = { .commit_seq = wlr_output->commit_seq + 1, .presented = true, }; output_defer_present(wlr_output, present_event); - - wl_event_source_timer_update(output->frame_timer, output->frame_delay); } return true; @@ -94,7 +76,6 @@ static void output_destroy(struct wlr_output *wlr_output) { wlr_output_finish(wlr_output); wl_list_remove(&output->link); - wl_event_source_remove(output->frame_timer); free(output); } @@ -110,12 +91,6 @@ bool wlr_output_is_headless(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } -static int signal_frame(void *data) { - struct wlr_headless_output *output = data; - wlr_output_send_frame(&output->wlr_output); - return 0; -} - struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, unsigned int width, unsigned int height) { struct wlr_headless_backend *backend = @@ -136,8 +111,6 @@ struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, wlr_output_init(wlr_output, &backend->backend, &output_impl, backend->event_loop, &state); wlr_output_state_finish(&state); - output_update_refresh(output, 0); - size_t output_num = ++last_output_num; char name[64]; @@ -148,8 +121,6 @@ struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, snprintf(description, sizeof(description), "Headless output %zu", output_num); wlr_output_set_description(wlr_output, description); - output->frame_timer = wl_event_loop_add_timer(backend->event_loop, signal_frame, output); - wl_list_insert(&backend->outputs, &output->link); if (backend->started) { diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 0d294b543..e171ff544 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -581,6 +581,11 @@ bool wlr_backend_is_wl(const 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_event_loop_destroy(struct wl_listener *listener, void *data) { struct wlr_wl_backend *wl = wl_container_of(listener, wl, event_loop_destroy); backend_destroy(&wl->backend); diff --git a/backend/wayland/output.c b/backend/wayland/output.c index c3442f411..d677c751b 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -72,8 +72,6 @@ static void surface_frame_callback(void *data, struct wl_callback *cb, assert(output->frame_callback == cb); wl_callback_destroy(cb); output->frame_callback = NULL; - - wlr_output_send_frame(&output->wlr_output); } static const struct wl_callback_listener frame_listener = { diff --git a/backend/x11/output.c b/backend/x11/output.c index 40e601f0b..c9c8cfdd5 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -795,8 +795,6 @@ void handle_x11_present_event(struct wlr_x11_backend *x11, }; timespec_from_nsec(&present_event.when, complete_notify->ust * 1000); wlr_output_send_present(&output->wlr_output, &present_event); - - wlr_output_send_frame(&output->wlr_output); break; default: wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type); diff --git a/examples/cairo-buffer.c b/examples/cairo-buffer.c index 6392b2ac5..2eb3b7264 100644 --- a/examples/cairo-buffer.c +++ b/examples/cairo-buffer.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -104,10 +105,10 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) { struct output *output = calloc(1, sizeof(*output)); output->wlr = wlr_output; output->server = server; - output->frame.notify = output_handle_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); output->scene_output = wlr_scene_output_create(server->scene, wlr_output); + output->frame.notify = output_handle_frame; + wl_signal_add(&output->scene_output->frame_scheduler->events.frame, &output->frame); struct wlr_output_state state; wlr_output_state_init(&state); diff --git a/examples/embedded.c b/examples/embedded.c index f0f661198..647c8e38b 100644 --- a/examples/embedded.c +++ b/examples/embedded.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -220,10 +221,10 @@ int main(int argc, char *argv[]) { wl_subsurface_set_position(subsurface, 20, 20); struct wlr_output *output = wlr_wl_output_create_from_surface(backend, child_surface); wlr_output_init_render(output, allocator, renderer); - scene_output = wlr_scene_output_create(scene, output); + scene_output = wlr_scene_output_create(scene, output); output_frame.notify = output_handle_frame; - wl_signal_add(&output->events.frame, &output_frame); + wl_signal_add(&scene_output->frame_scheduler->events.frame, &output_frame); struct wlr_output_state state; wlr_output_state_init(&state); diff --git a/examples/output-layers.c b/examples/output-layers.c index 0535ff0b1..187eaa4de 100644 --- a/examples/output-layers.c +++ b/examples/output-layers.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,7 @@ struct output { struct wl_list link; struct server *server; struct wlr_output *wlr_output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_list surfaces; struct wl_listener frame; @@ -155,6 +157,8 @@ static void output_handle_frame(struct wl_listener *listener, void *data) { } wl_array_release(&layers_arr); + + wlr_frame_scheduler_schedule_frame(output->frame_scheduler); } static void server_handle_new_output(struct wl_listener *listener, void *data) { @@ -167,10 +171,12 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) { output->wlr_output = wlr_output; output->server = server; wl_list_init(&output->surfaces); - output->frame.notify = output_handle_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); wl_list_insert(&server->outputs, &output->link); + output->frame_scheduler = wlr_frame_scheduler_autocreate(wlr_output); + output->frame.notify = output_handle_frame; + wl_signal_add(&output->frame_scheduler->events.frame, &output->frame); + struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, true); @@ -181,6 +187,8 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) { wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); + wlr_frame_scheduler_schedule_frame(output->frame_scheduler); + wlr_output_create_global(wlr_output, server->wl_display); } diff --git a/examples/output-layout.c b/examples/output-layout.c index c481ddf80..607b9703e 100644 --- a/examples/output-layout.c +++ b/examples/output-layout.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ struct sample_state { struct sample_output { struct sample_state *sample; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; }; @@ -143,6 +145,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_render_pass_submit(pass); wlr_output_commit_state(wlr_output, &output_state); wlr_output_state_finish(&output_state); + + wlr_frame_scheduler_schedule_frame(output->frame_scheduler); } static void update_velocities(struct sample_state *sample, @@ -157,6 +161,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { wlr_output_layout_remove(sample->layout, sample_output->output); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -170,7 +175,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { wlr_output_layout_add_auto(sample->layout, output); sample_output->output = output; sample_output->sample = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -184,6 +190,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void keyboard_key_notify(struct wl_listener *listener, void *data) { diff --git a/examples/pointer.c b/examples/pointer.c index f3ef5435e..fa306f007 100644 --- a/examples/pointer.c +++ b/examples/pointer.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ struct touch_point { struct sample_output { struct sample_state *state; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; }; @@ -115,6 +117,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_render_pass_submit(pass); wlr_output_commit_state(wlr_output, &output_state); wlr_output_state_finish(&output_state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void handle_cursor_motion(struct wl_listener *listener, void *data) { @@ -255,6 +259,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { wlr_output_layout_remove(sample->layout, sample_output->output); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -267,7 +272,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); sample_output->output = output; sample_output->state = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -282,6 +288,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } diff --git a/examples/rotation.c b/examples/rotation.c index 18bfbd38d..33b85b9a4 100644 --- a/examples/rotation.c +++ b/examples/rotation.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ struct sample_state { struct sample_output { struct sample_state *sample; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; float x_offs, y_offs; @@ -93,6 +95,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { sample_output->y_offs = 0; } sample->last_frame = now; + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void update_velocities(struct sample_state *sample, @@ -108,6 +112,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -123,7 +128,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { sample_output->output = output; sample_output->sample = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -139,6 +145,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void keyboard_key_notify(struct wl_listener *listener, void *data) { diff --git a/examples/scene-graph.c b/examples/scene-graph.c index 421986534..88dc792d0 100644 --- a/examples/scene-graph.c +++ b/examples/scene-graph.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -74,10 +75,10 @@ static void server_handle_new_output(struct wl_listener *listener, void *data) { struct output *output = calloc(1, sizeof(*output)); output->wlr = wlr_output; output->server = server; - output->frame.notify = output_handle_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); output->scene_output = wlr_scene_output_create(server->scene, wlr_output); + output->frame.notify = output_handle_frame; + wl_signal_add(&output->scene_output->frame_scheduler->events.frame, &output->frame); struct wlr_output_state state; wlr_output_state_init(&state); diff --git a/examples/simple.c b/examples/simple.c index d16708654..b016b7b19 100644 --- a/examples/simple.c +++ b/examples/simple.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ struct sample_state { struct sample_output { struct sample_state *sample; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; }; @@ -78,6 +80,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); sample->last_frame = now; + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void output_remove_notify(struct wl_listener *listener, void *data) { @@ -86,6 +90,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { wlr_log(WLR_DEBUG, "Output removed"); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -99,7 +104,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); sample_output->output = output; sample_output->sample = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -113,6 +120,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void keyboard_key_notify(struct wl_listener *listener, void *data) { diff --git a/examples/tablet.c b/examples/tablet.c index 78666fff7..fd430fb06 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,7 @@ struct tablet_pad_state { struct sample_output { struct sample_state *sample; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; }; @@ -160,6 +162,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_output_commit_state(wlr_output, &output_state); wlr_output_state_finish(&output_state); sample->last_frame = now; + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void tablet_tool_axis_notify(struct wl_listener *listener, void *data) { @@ -261,6 +265,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -273,7 +278,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); sample_output->output = output; sample_output->sample = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -287,6 +294,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void keyboard_key_notify(struct wl_listener *listener, void *data) { diff --git a/examples/touch.c b/examples/touch.c index b891837a4..eb5006117 100644 --- a/examples/touch.c +++ b/examples/touch.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ struct touch_state { struct sample_output { struct sample_state *sample; struct wlr_output *output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener destroy; }; @@ -96,6 +98,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_output_commit_state(wlr_output, &output_state); wlr_output_state_finish(&output_state); sample->last_frame = now; + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void touch_down_notify(struct wl_listener *listener, void *data) { @@ -164,6 +168,7 @@ static void output_remove_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy); wl_list_remove(&sample_output->frame.link); wl_list_remove(&sample_output->destroy.link); + wlr_frame_scheduler_destroy(sample_output->frame_scheduler); free(sample_output); } @@ -176,7 +181,9 @@ static void new_output_notify(struct wl_listener *listener, void *data) { struct sample_output *sample_output = calloc(1, sizeof(*sample_output)); sample_output->output = output; sample_output->sample = sample; - wl_signal_add(&output->events.frame, &sample_output->frame); + + sample_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + wl_signal_add(&sample_output->frame_scheduler->events.frame, &sample_output->frame); sample_output->frame.notify = output_frame_notify; wl_signal_add(&output->events.destroy, &sample_output->destroy); sample_output->destroy.notify = output_remove_notify; @@ -190,6 +197,8 @@ static void new_output_notify(struct wl_listener *listener, void *data) { } wlr_output_commit_state(output, &state); wlr_output_state_finish(&state); + + wlr_frame_scheduler_schedule_frame(sample_output->frame_scheduler); } static void keyboard_key_notify(struct wl_listener *listener, void *data) { diff --git a/include/backend/headless.h b/include/backend/headless.h index 21df86ea6..8f564b5a9 100644 --- a/include/backend/headless.h +++ b/include/backend/headless.h @@ -4,8 +4,6 @@ #include #include -#define HEADLESS_DEFAULT_REFRESH (60 * 1000) // 60 Hz - struct wlr_headless_backend { struct wlr_backend backend; struct wl_event_loop *event_loop; @@ -19,9 +17,6 @@ struct wlr_headless_output { struct wlr_headless_backend *backend; struct wl_list link; - - struct wl_event_source *frame_timer; - int frame_delay; // ms }; struct wlr_headless_backend *headless_backend_from_backend( diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 82d3e40fd..021749c27 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -67,6 +67,9 @@ struct wlr_vk_device { struct wlr_drm_format_set dmabuf_render_formats; struct wlr_drm_format_set dmabuf_texture_formats; struct wlr_drm_format_set shm_texture_formats; + + float timestamp_period; + uint32_t timestamp_valid_bits; }; // Tries to find the VkPhysicalDevice for the given drm fd. @@ -412,6 +415,12 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer); // finished execution. bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd); +struct wlr_vk_render_timer { + struct wlr_render_timer base; + struct wlr_vk_renderer *renderer; + VkQueryPool query_pool; +}; + struct wlr_vk_render_pass_texture { struct wlr_vk_texture *texture; @@ -436,6 +445,8 @@ struct wlr_vk_render_pass { struct wlr_drm_syncobj_timeline *signal_timeline; uint64_t signal_point; + struct wlr_vk_render_timer *timer; + struct wl_array textures; // struct wlr_vk_render_pass_texture }; diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h index 7a41b3d09..d79d79b6b 100644 --- a/include/wlr/backend/wayland.h +++ b/include/wlr/backend/wayland.h @@ -48,6 +48,11 @@ struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend */ bool wlr_backend_is_wl(const 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/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/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 6da2c2c54..2481b9b40 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -117,12 +117,6 @@ void wlr_output_finish(struct wlr_output *output); * output changes. */ void wlr_output_update_needs_frame(struct wlr_output *output); -/** - * Send a frame event. - * - * See wlr_output.events.frame. - */ -void wlr_output_send_frame(struct wlr_output *output); /** * Send a present event. * diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h new file mode 100644 index 000000000..f7b0ddf94 --- /dev/null +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -0,0 +1,89 @@ +/* + * 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 +#include + +struct wlr_frame_scheduler_impl; +struct wlr_render_timer; + +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); +/** + * The Wayland 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_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. + */ +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); + +/** + * 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_output.h b/include/wlr/types/wlr_output.h index c8e44b0e6..ece2b5b10 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -219,10 +219,6 @@ struct wlr_output { // true, changes may either succeed or fail. bool adaptive_sync_supported; - bool needs_frame; - // damage for cursors and fullscreen surface, in output-local coordinates - bool frame_pending; - // true for example with VR headsets bool non_desktop; @@ -230,8 +226,6 @@ struct wlr_output { uint32_t commit_seq; struct { - // Request to render a frame - struct wl_signal frame; // Emitted when software cursors or backend-specific logic damage the // output struct wl_signal damage; // struct wlr_output_event_damage @@ -252,7 +246,6 @@ struct wlr_output { struct wl_signal destroy; } events; - struct wl_event_source *idle_frame; struct wl_event_source *idle_done; int attach_render_locks; // number of locks forcing rendering @@ -406,11 +399,6 @@ bool wlr_output_test_state(struct wlr_output *output, */ bool wlr_output_commit_state(struct wlr_output *output, const struct wlr_output_state *state); -/** - * Manually schedules a `frame` event. If a `frame` event is already pending, - * it is a no-op. - */ -void wlr_output_schedule_frame(struct wlr_output *output); /** * Returns the maximum length of each gamma ramp, or 0 if unsupported. */ diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index f6f97cfea..86f1f7934 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -224,6 +224,7 @@ struct wlr_scene_output { struct wlr_output *output; struct wl_list link; // wlr_scene.outputs struct wlr_scene *scene; + struct wlr_frame_scheduler *frame_scheduler; struct wlr_addon addon; struct wlr_damage_ring damage_ring; @@ -258,7 +259,6 @@ struct wlr_scene_output { struct wl_listener output_commit; struct wl_listener output_damage; - struct wl_listener output_needs_frame; struct wl_list damage_highlight_regions; @@ -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 */ @@ -592,10 +589,15 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output); */ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, int lx, int ly); +/** + * Replace the frame scheduler for this scene output, destroying the previous + * scheduler. If scene output currently needs a new frame, a frame will be + * scheduled on the new frame scheduler. + */ +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. @@ -610,12 +612,6 @@ struct wlr_scene_output_state_options { struct wlr_swapchain *swapchain; }; -/** - * Returns true if scene wants to render a new frame. False, if no new frame - * is needed and an output commit can be skipped for the current frame. - */ -bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output); - /** * Render and commit an output. */ @@ -628,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/vulkan/pass.c b/render/vulkan/pass.c index becd060f4..01e8fbd7a 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -295,6 +295,11 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { vkCmdEndRenderPass(render_cb->vk); + if (pass->timer != NULL) { + vkCmdWriteTimestamp(render_cb->vk, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + pass->timer->query_pool, 1); + } + size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); size_t render_wait_cap = (1 + pass_textures_len) * WLR_DMABUF_MAX_PLANES; render_wait = calloc(render_wait_cap, sizeof(*render_wait)); @@ -1280,6 +1285,14 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); } + struct wlr_vk_render_timer *timer = NULL; + if (options != NULL && options->timer != NULL) { + timer = wl_container_of(options->timer, timer, base); + vkCmdResetQueryPool(cb->vk, timer->query_pool, 0, 2); + vkCmdWriteTimestamp(cb->vk, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timer->query_pool, 0); + } + int width = buffer->wlr_buffer->width; int height = buffer->wlr_buffer->height; VkRect2D rect = { .extent = { width, height } }; @@ -1308,5 +1321,6 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend pass->render_buffer_out = buffer_out; pass->render_setup = render_setup; pass->command_buffer = cb; + pass->timer = timer; return pass; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index e04f95a0a..72d1fb52a 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1556,6 +1556,83 @@ static struct wlr_render_pass *vulkan_begin_buffer_pass(struct wlr_renderer *wlr return &render_pass->base; } +static const struct wlr_render_timer_impl render_timer_impl; + +static struct wlr_render_timer *vulkan_render_timer_create( + struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + if (renderer->dev->timestamp_valid_bits == 0) { + wlr_log(WLR_ERROR, "Failed to create render timer: " + "timestamp queries not supported by queue family"); + return NULL; + } + + struct wlr_vk_render_timer *timer = calloc(1, sizeof(*timer)); + if (!timer) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + VkQueryPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, + .queryType = VK_QUERY_TYPE_TIMESTAMP, + .queryCount = 2, + }; + VkResult res = vkCreateQueryPool(renderer->dev->dev, &pool_info, + NULL, &timer->query_pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateQueryPool", res); + free(timer); + return NULL; + } + + timer->base.impl = &render_timer_impl; + timer->renderer = renderer; + return &timer->base; +} + +static int vulkan_render_timer_get_duration_ns( + struct wlr_render_timer *wlr_timer) { + struct wlr_vk_render_timer *timer = + wl_container_of(wlr_timer, timer, base); + struct wlr_vk_renderer *renderer = timer->renderer; + + // Layout: [ timestamp1, avail1, timestamp2, avail2 ] + uint64_t data[4] = {0}; + VkResult res = vkGetQueryPoolResults(renderer->dev->dev, + timer->query_pool, 0, 2, sizeof(data), data, + 2 * sizeof(uint64_t), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT); + if (res == VK_NOT_READY || data[1] == 0 || data[3] == 0) { + wlr_log(WLR_ERROR, "Failed to get render duration: " + "timestamp query results not yet ready"); + return -1; + } + if (res != VK_SUCCESS) { + wlr_vk_error("vkGetQueryPoolResults", res); + return -1; + } + + uint64_t ticks = data[2] - data[0]; + if (renderer->dev->timestamp_valid_bits < 64) { + ticks &= (1ULL << renderer->dev->timestamp_valid_bits) - 1; + } + return (int)(ticks * renderer->dev->timestamp_period); +} + +static void vulkan_render_timer_destroy( + struct wlr_render_timer *wlr_timer) { + struct wlr_vk_render_timer *timer = + wl_container_of(wlr_timer, timer, base); + vkDestroyQueryPool(timer->renderer->dev->dev, timer->query_pool, NULL); + free(timer); +} + +static const struct wlr_render_timer_impl render_timer_impl = { + .get_duration_ns = vulkan_render_timer_get_duration_ns, + .destroy = vulkan_render_timer_destroy, +}; + static const struct wlr_renderer_impl renderer_impl = { .get_texture_formats = vulkan_get_texture_formats, .get_render_formats = vulkan_get_render_formats, @@ -1563,6 +1640,7 @@ static const struct wlr_renderer_impl renderer_impl = { .get_drm_fd = vulkan_get_drm_fd, .texture_from_buffer = vulkan_texture_from_buffer, .begin_buffer_pass = vulkan_begin_buffer_pass, + .render_timer_create = vulkan_render_timer_create, }; // Initializes the VkDescriptorSetLayout and VkPipelineLayout needed diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index b4a5e0e11..3877ec2a7 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -488,10 +488,15 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, graphics_found = queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT; if (graphics_found) { dev->queue_family = i; + dev->timestamp_valid_bits = queue_props[i].timestampValidBits; break; } } assert(graphics_found); + + VkPhysicalDeviceProperties phdev_props; + vkGetPhysicalDeviceProperties(phdev, &phdev_props); + dev->timestamp_period = phdev_props.limits.timestampPeriod; } bool exportable_semaphore = false, importable_semaphore = false; 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/tinywl/tinywl.c b/tinywl/tinywl.c index fe242e1a9..d29eb2930 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ struct tinywl_output { struct wl_list link; struct tinywl_server *server; struct wlr_output *wlr_output; + struct wlr_frame_scheduler *frame_scheduler; struct wl_listener frame; struct wl_listener request_state; struct wl_listener destroy; @@ -603,6 +605,7 @@ static void output_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&output->request_state.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); + wlr_frame_scheduler_destroy(output->frame_scheduler); free(output); } @@ -641,10 +644,6 @@ static void server_new_output(struct wl_listener *listener, void *data) { output->wlr_output = wlr_output; output->server = server; - /* Sets up a listener for the frame event. */ - output->frame.notify = output_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); - /* Sets up a listener for the state request event. */ output->request_state.notify = output_request_state; wl_signal_add(&wlr_output->events.request_state, &output->request_state); @@ -668,6 +667,10 @@ static void server_new_output(struct wl_listener *listener, void *data) { wlr_output); struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output); wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); + + /* Sets up a listener for the frame event. */ + output->frame.notify = output_frame; + wl_signal_add(&scene_output->frame_scheduler->events.frame, &output->frame); } static void xdg_toplevel_map(struct wl_listener *listener, void *data) { diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index 7d4b8928a..29f38b268 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "types/wlr_output.h" @@ -134,11 +135,8 @@ static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { static void source_request_frame(struct wlr_ext_image_capture_source_v1 *base, bool schedule_frame) { struct scene_node_source *source = wl_container_of(base, source, base); - if (source->output.frame_pending) { - wlr_output_send_frame(&source->output); - } - if (schedule_frame) { - wlr_output_update_needs_frame(&source->output); + if (schedule_frame && source->scene_output != NULL) { + wlr_frame_scheduler_schedule_frame(source->scene_output->frame_scheduler); } } @@ -246,6 +244,12 @@ static bool output_commit(struct wlr_output *output, const struct wlr_output_sta pixman_region32_fini(&full_damage); + struct wlr_output_event_present present_event = { + .commit_seq = output->commit_seq + 1, + .presented = true, + }; + output_defer_present(output, present_event); + return true; } @@ -283,11 +287,7 @@ static void source_handle_output_frame(struct wl_listener *listener, void *data) return; } - if (!wlr_scene_output_needs_frame(source->scene_output)) { - return; - } - - // We can only emit frames with damage + // Only render when there's actual damage to commit if (!pixman_region32_empty(&source->scene_output->pending_commit_damage)) { source_render(source); } @@ -331,7 +331,7 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_w wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy); source->output_frame.notify = source_handle_output_frame; - wl_signal_add(&source->output.events.frame, &source->output_frame); + wl_signal_add(&source->scene_output->frame_scheduler->events.frame, &source->output_frame); return &source->base; } diff --git a/types/meson.build b/types/meson.build index bc3d32cd9..63e116d9b 100644 --- a/types/meson.build +++ b/types/meson.build @@ -50,6 +50,7 @@ wlr_files += files( 'wlr_export_dmabuf_v1.c', 'wlr_ext_background_effect_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..bf1d73ac8 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -367,7 +367,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_list_init(&output->layers); wl_list_init(&output->resources); - wl_signal_init(&output->events.frame); + wl_signal_init(&output->events.damage); wl_signal_init(&output->events.needs_frame); wl_signal_init(&output->events.precommit); @@ -397,7 +397,6 @@ void wlr_output_finish(struct wlr_output *output) { wl_signal_emit_mutable(&output->events.destroy, output); wlr_addon_set_finish(&output->addons); - assert(wl_list_empty(&output->events.frame.listener_list)); assert(wl_list_empty(&output->events.damage.listener_list)); assert(wl_list_empty(&output->events.needs_frame.listener_list)); assert(wl_list_empty(&output->events.precommit.listener_list)); @@ -430,10 +429,6 @@ void wlr_output_finish(struct wlr_output *output) { wlr_swapchain_destroy(output->swapchain); - if (output->idle_frame != NULL) { - wl_event_source_remove(output->idle_frame); - } - if (output->idle_done != NULL) { wl_event_source_remove(output->idle_done); } @@ -763,12 +758,6 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st return false; } - if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && - output->idle_frame != NULL) { - wl_event_source_remove(output->idle_frame); - output->idle_frame = NULL; - } - struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -785,11 +774,6 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state) { output->commit_seq++; - if (output_pending_enabled(output, state)) { - output->frame_pending = true; - output->needs_frame = false; - } - output_apply_state(output, state); } @@ -844,37 +828,6 @@ bool wlr_output_commit_state(struct wlr_output *output, return true; } -void wlr_output_send_frame(struct wlr_output *output) { - output->frame_pending = false; - if (output->enabled) { - wl_signal_emit_mutable(&output->events.frame, output); - } -} - -static void schedule_frame_handle_idle_timer(void *data) { - struct wlr_output *output = data; - output->idle_frame = NULL; - if (!output->frame_pending) { - wlr_output_send_frame(output); - } -} - -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. - wlr_output_update_needs_frame(output); - - if (output->frame_pending || output->idle_frame != NULL) { - return; - } - - // We're using an idle timer here in case a buffer swap happens right after - // this function is called - output->idle_frame = wl_event_loop_add_idle(output->event_loop, - schedule_frame_handle_idle_timer, output); -} - void wlr_output_send_present(struct wlr_output *output, struct wlr_output_event_present *event) { assert(event); @@ -988,10 +941,6 @@ size_t wlr_output_get_gamma_size(struct wlr_output *output) { } void wlr_output_update_needs_frame(struct wlr_output *output) { - if (output->needs_frame) { - return; - } - output->needs_frame = true; wl_signal_emit_mutable(&output->events.needs_frame, output); } diff --git a/types/scene/surface.c b/types/scene/surface.c index e6ea1333a..b5d1a314e 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -364,7 +365,7 @@ static void handle_scene_surface_surface_commit( if (!wl_list_empty(&surface->surface->current.frame_callback_list) && surface->buffer->primary_output != NULL && enabled) { - wlr_output_schedule_frame(surface->buffer->primary_output->output); + wlr_frame_scheduler_schedule_frame(surface->buffer->primary_output->frame_scheduler); } } diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 7231422e8..59c1500e6 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -362,7 +363,7 @@ static void scene_output_damage(struct wlr_scene_output *scene_output, pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height); if (!pixman_region32_empty(&clipped)) { - wlr_output_schedule_frame(scene_output->output); + wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler); wlr_damage_ring_add(&scene_output->damage_ring, &clipped); pixman_region32_union(&scene_output->pending_commit_damage, @@ -1576,7 +1577,7 @@ static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener * output->gamma_lut = event->control; wlr_color_transform_unref(output->gamma_lut_color_transform); output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control); - wlr_output_schedule_frame(output->output); + wlr_frame_scheduler_schedule_frame(output->frame_scheduler); } static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *listener, @@ -1691,7 +1692,7 @@ static void scene_output_handle_commit(struct wl_listener *listener, void *data) if (scene_output->scene->debug_damage_option == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && !wl_list_empty(&scene_output->damage_highlight_regions)) { - wlr_output_schedule_frame(scene_output->output); + wlr_frame_scheduler_schedule_frame(scene_output->frame_scheduler); } // Next time the output is enabled, try to re-apply the gamma LUT @@ -1720,12 +1721,6 @@ static void scene_output_handle_damage(struct wl_listener *listener, void *data) pixman_region32_fini(&damage); } -static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) { - struct wlr_scene_output *scene_output = wl_container_of(listener, - scene_output, output_needs_frame); - wlr_output_schedule_frame(scene_output->output); -} - struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, struct wlr_output *output) { struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_output)); @@ -1733,6 +1728,12 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, return NULL; } + scene_output->frame_scheduler = wlr_frame_scheduler_autocreate(output); + if (scene_output->frame_scheduler == NULL) { + free(scene_output); + return NULL; + } + scene_output->output = output; scene_output->scene = scene; wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); @@ -1778,11 +1779,10 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, scene_output->output_damage.notify = scene_output_handle_damage; wl_signal_add(&output->events.damage, &scene_output->output_damage); - scene_output->output_needs_frame.notify = scene_output_handle_needs_frame; - wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame); - scene_output_update_geometry(scene_output, false); + scene_output->render_timer = wlr_render_timer_create(output->renderer); + return scene_output; } @@ -1792,6 +1792,16 @@ static void highlight_region_destroy(struct highlight_region *damage) { free(damage); } +void wlr_scene_output_set_frame_scheduler(struct wlr_scene_output *scene_output, + struct wlr_frame_scheduler *scheduler) { + bool needs_frame = scene_output->frame_scheduler->needs_frame; + wlr_frame_scheduler_destroy(scene_output->frame_scheduler); + scene_output->frame_scheduler = scheduler; + if (needs_frame) { + wlr_frame_scheduler_schedule_frame(scheduler); + } +} + void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { if (scene_output == NULL) { return; @@ -1810,12 +1820,13 @@ 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); wl_list_remove(&scene_output->link); wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_damage.link); - wl_list_remove(&scene_output->output_needs_frame.link); if (scene_output->in_timeline != NULL) { wlr_drm_syncobj_timeline_signal(scene_output->in_timeline, UINT64_MAX); wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); @@ -2145,18 +2156,8 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_SUCCESS; } -bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output) { - return scene_output->output->needs_frame || - !pixman_region32_empty(&scene_output->pending_commit_damage) || - scene_output->gamma_lut_changed; -} - bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, const struct wlr_scene_output_state_options *options) { - if (!wlr_scene_output_needs_frame(scene_output)) { - return true; - } - bool ok = false; struct wlr_output_state state; wlr_output_state_init(&state); @@ -2281,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. @@ -2434,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; } @@ -2459,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 @@ -2484,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, @@ -2608,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 new file mode 100644 index 000000000..04db2a6e0 --- /dev/null +++ b/types/wlr_frame_scheduler.c @@ -0,0 +1,502 @@ +#include +#include +#include +#include +#include +#include +#include +#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; +} + +// This scheduler builds on idle_frame_scheduler and uses Wayland's frame callbacks for driving the +// render loop. +struct wl_scheduler { + struct idle_frame_scheduler base; + struct wl_callback *frame; + struct wl_listener precommit; +}; + +static void wl_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) { + struct wl_scheduler *scheduler = wl_container_of(wlr_scheduler, scheduler, base.base); + idle_frame_scheduler_finish(&scheduler->base); + wl_list_remove(&scheduler->precommit.link); + wl_callback_destroy(scheduler->frame); + free(scheduler); +} + +static void wl_scheduler_handle_frame(void *data, struct wl_callback *cb, uint32_t time) { + struct wl_scheduler *scheduler = data; + assert(scheduler->frame == cb); + idle_frame_scheduler_emit_frame(&scheduler->base); +} + +static const struct wl_callback_listener wl_scheduler_frame_listener = { + .done = wl_scheduler_handle_frame, +}; + +static void wl_scheduler_handle_precommit(struct wl_listener *listener, void *data) { + struct wl_scheduler *scheduler = wl_container_of(listener, scheduler, precommit); + struct wlr_output_event_precommit *precommit = data; + if (!output_pending_enabled(precommit->output, precommit->state)) { + 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_scheduler_frame_listener, scheduler); +} + +static const struct wlr_frame_scheduler_impl wl_scheduler_impl = { + .schedule_frame = idle_frame_scheduler_schedule_frame, + .destroy = wl_scheduler_destroy, +}; + +static struct wlr_frame_scheduler *wl_scheduler_create(struct wlr_output *output) { + if (!wlr_output_is_wl(output)) { + return NULL; + } + + struct wl_scheduler *scheduler = calloc(1, sizeof(*scheduler)); + if (!scheduler) { + return NULL; + } + wlr_frame_scheduler_init(&scheduler->base.base, &wl_scheduler_impl, output); + scheduler->precommit.notify = wl_scheduler_handle_precommit; + wl_signal_add(&output->events.precommit, &scheduler->precommit); + + 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; +} + +// 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"); + return wl_scheduler_create(output); + } + + if (wlr_output_is_headless(output)) { + return wlr_interval_scheduler_create(output); + } + + return wlr_present_scheduler_create(output); +}