From 0e070e93d85fe78be7ef0f23f246bcb42bea5d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20G=C3=B3mez?= Date: Mon, 1 Dec 2025 17:23:46 -0500 Subject: [PATCH 1/3] util: add helper to get time in nanoseconds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergio Gómez --- include/util/time.h | 5 +++++ util/time.c | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/include/util/time.h b/include/util/time.h index dd164bf2f..fd2bf9626 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -6,6 +6,11 @@ static const long NSEC_PER_SEC = 1000000000; +/** + * Get the current time, in nanoseconds. + */ +int64_t get_current_time_nsec(void); + /** * Get the current time, in milliseconds. */ diff --git a/util/time.c b/util/time.c index 1a8f32969..7db0893b4 100644 --- a/util/time.c +++ b/util/time.c @@ -22,6 +22,12 @@ int64_t get_current_time_msec(void) { return timespec_to_msec(&now); } +int64_t get_current_time_nsec(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return timespec_to_nsec(&now); +} + void timespec_sub(struct timespec *r, const struct timespec *a, const struct timespec *b) { r->tv_sec = a->tv_sec - b->tv_sec; From e57244c1bac982bbee4b9c17c64cbfb3ea23bd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20G=C3=B3mez?= Date: Wed, 26 Nov 2025 10:34:55 -0500 Subject: [PATCH 2/3] commit-timing-v1: new protocol implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergio Gómez --- include/wlr/types/wlr_commit_timing_v1.h | 91 ++++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_commit_timing_v1.c | 377 +++++++++++++++++++++++ 4 files changed, 470 insertions(+) create mode 100644 include/wlr/types/wlr_commit_timing_v1.h create mode 100644 types/wlr_commit_timing_v1.c diff --git a/include/wlr/types/wlr_commit_timing_v1.h b/include/wlr/types/wlr_commit_timing_v1.h new file mode 100644 index 000000000..c83a49b9c --- /dev/null +++ b/include/wlr/types/wlr_commit_timing_v1.h @@ -0,0 +1,91 @@ +/* + * 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_TIMING_H +#define WLR_TYPES_WLR_TIMING_H + +#include + +struct wlr_commit_timing_manager_v1_new_timer_event { + struct wlr_commit_timer_v1 *timer; +}; + +struct wlr_commit_timer_v1_commit { + struct wlr_commit_timer_v1 *timer; + + /** + * Commits' wlr_surface pending sequence when locking through + * wlr_surface_lock_pending(). Used to unlock the commit when the 'unlock_timer' goes off. + */ + uint32_t pending_seq; + + /** + * Timer for when this commit should be unlocked for presentation. + */ + struct wl_event_source *unlock_source; + + struct wl_list link; // wlr_commit_timer_v1::commits +}; + +struct wlr_commit_timer_v1_state { + uint64_t base_present_nsec; + int32_t refresh; + uint64_t timestamp_nsec; // holds the timestamp for the .set_timestamp protocol request +}; + +struct wlr_commit_timer_v1 { + struct wlr_commit_timing_manager_v1 *timing_manager; + + struct wl_resource *resource; + struct wl_display *wl_display; + struct wlr_addon addon; + + struct wlr_surface *surface; + struct wlr_output *output; + + struct wlr_commit_timer_v1_state state; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wl_listener client_commit; + struct wl_listener output_present; + struct wl_listener output_destroy; + } WLR_PRIVATE; + + struct wl_list commits; // wlr_commit_timer_v1_commit.link + + struct wl_list scene_link; // wlr_scene.commit_timers +}; + +struct wlr_commit_timing_manager_v1 { + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_timer; + struct wl_signal destroy; + } events; +}; + +/** + * Set the output from which we will get the refresh cycle timings for this wlr_commit_timer_v1. + */ +void wlr_commit_timer_v1_set_output(struct wlr_commit_timer_v1 *timer, struct wlr_output *output); + +/** + * Create the wp_commit_timing_manager_v1_interface global, which can be used by clients to + * set timestamps for surface commit request presentation. + */ +struct wlr_commit_timing_manager_v1 *wlr_commit_timing_manager_v1_create(struct wl_display *display, + uint32_t version); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 613d18018..61292288f 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -27,6 +27,7 @@ protocols = { 'alpha-modifier-v1': wl_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', 'color-management-v1': wl_protocol_dir / 'staging/color-management/color-management-v1.xml', 'color-representation-v1': wl_protocol_dir / 'staging/color-representation/color-representation-v1.xml', + 'commit-timing-v1': wl_protocol_dir / 'staging/commit-timing/commit-timing-v1.xml', 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', diff --git a/types/meson.build b/types/meson.build index 402fd3e11..7b6c2a6f0 100644 --- a/types/meson.build +++ b/types/meson.build @@ -76,6 +76,7 @@ wlr_files += files( 'wlr_presentation_time.c', 'wlr_primary_selection_v1.c', 'wlr_primary_selection.c', + 'wlr_commit_timing_v1.c', 'wlr_region.c', 'wlr_relative_pointer_v1.c', 'wlr_screencopy_v1.c', diff --git a/types/wlr_commit_timing_v1.c b/types/wlr_commit_timing_v1.c new file mode 100644 index 000000000..3ab97f54c --- /dev/null +++ b/types/wlr_commit_timing_v1.c @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "commit-timing-v1-protocol.h" +#include "util/time.h" + +#define TIMING_MANAGER_VERSION 1 + +static void commit_destroy(struct wlr_commit_timer_v1_commit *commit) { + wl_event_source_remove(commit->unlock_source); + // the remove from the list must happen before unlocking the commit, since the commit might end + // up calling wlr_commit_timer_v1_set_output(), which traverses this list. + wl_list_remove(&commit->link); + wlr_surface_unlock_cached(commit->timer->surface, commit->pending_seq); + free(commit); +} + +static int handle_commit_timerfd(int fd, uint32_t mask, void *data) { + struct wlr_commit_timer_v1_commit *commit = data; + commit_destroy(commit); + close(fd); + return 0; +} + +static void timer_handle_output_present(struct wl_listener *listener, void *data) { + struct wlr_commit_timer_v1 *timer = + wl_container_of(listener, timer, output_present); + struct wlr_output_event_present *event = data; + + // we constantly check if the refresh rate has changed, to reset accordingly + if (timer->state.refresh != event->output->refresh) { + wlr_commit_timer_v1_set_output(timer, event->output); + } + + // we need to have just one presentation time so that, together with the refresh rate, we + // can know the refresh cycle offset for future presentations. + if (event->presented && !timer->state.base_present_nsec) { + timer->state.base_present_nsec = timespec_to_nsec(&event->when); + } +} + +static bool target_is_in_past(uint64_t target_nsec) { + return target_nsec < (uint64_t)get_current_time_nsec(); +} + +static int mhz_to_nsec(int mhz) { + assert(mhz != 0); + return 1000000000000LL / mhz; +} + +static uint64_t timer_get_target_nsec(const struct wlr_commit_timer_v1 *timer) { + struct wlr_output *output = timer->output; + uint64_t target_nsec = timer->state.timestamp_nsec; + + // ignore the request if the output is not stable yet, or the timestamp is invalid + if (!output || !timer->state.base_present_nsec || !target_nsec || + target_is_in_past(target_nsec)) { + target_nsec = 0; + goto out; + } + // if output has no refresh rate, use requested timestamp as is + if (!output->refresh) { + goto out; + } + + uint64_t refresh_nsec = mhz_to_nsec(output->refresh); + uint64_t cycle_phase_nsec = timer->state.base_present_nsec % refresh_nsec; + + uint64_t round_to_nearest_refresh_nsec = target_nsec; + round_to_nearest_refresh_nsec -= cycle_phase_nsec; + round_to_nearest_refresh_nsec += refresh_nsec/2; + round_to_nearest_refresh_nsec -= (round_to_nearest_refresh_nsec % refresh_nsec); + round_to_nearest_refresh_nsec += cycle_phase_nsec; + + target_nsec = round_to_nearest_refresh_nsec; + + if (timer->state.refresh) { + // Subtract 1 refresh cycle to the target time. + // This guarantees that the surface commit is unlocked before the + // compositor receives the .frame event for the refresh cycle we want to target. + target_nsec -= mhz_to_nsec(timer->state.refresh); + } + + target_nsec -= 500000; // subtract a 500us slop so that we don't miss the target + + // check if adjusted target time is in the past + if (target_is_in_past(target_nsec)) { + target_nsec = 0; + } +out: + return target_nsec; +} + +static void timer_handle_client_commit(struct wl_listener *listener, void *data) { + struct wlr_commit_timer_v1 *timer = + wl_container_of(listener, timer, client_commit); + struct wlr_commit_timer_v1_commit *commit = NULL; + + uint64_t target_nsec = timer_get_target_nsec(timer); + timer->state.timestamp_nsec = 0; // reset the timestamp + // if the target time is invalid, or if it's too close to the current time (<1ms), + // don't bother. + if (!target_nsec || target_nsec - get_current_time_nsec() < 1000000) { + goto out; + } + + commit = calloc(1, sizeof(*commit)); + if (!commit) { + wl_client_post_no_memory(wl_resource_get_client(timer->resource)); + goto out; + } + commit->timer = timer; + + int timerfd_unlock_commit_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + struct itimerspec itspec = { .it_interval = 0 }; + timespec_from_nsec(&itspec.it_value, target_nsec); + timerfd_settime(timerfd_unlock_commit_fd, TFD_TIMER_ABSTIME, &itspec, NULL); + commit->unlock_source = + wl_event_loop_add_fd(wl_display_get_event_loop(timer->wl_display), + timerfd_unlock_commit_fd, WL_EVENT_READABLE, handle_commit_timerfd, commit); + if (!commit->unlock_source) { + wl_client_post_no_memory(wl_resource_get_client(timer->resource)); + close(timerfd_unlock_commit_fd); + goto out; + } + + commit->pending_seq = wlr_surface_lock_pending(timer->surface); + + wl_list_insert(&timer->commits, &commit->link); + + return; +out: + free(commit); +} + +static const struct wp_commit_timer_v1_interface timer_impl; + +static struct wlr_commit_timer_v1 *wlr_commit_timer_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_commit_timer_v1_interface, + &timer_impl)); + return wl_resource_get_user_data(resource); +} + +static void timer_handle_set_timestamp(struct wl_client *client, + struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, + uint32_t tv_nsec) { + struct wlr_commit_timer_v1 *timer = wlr_commit_timer_v1_from_resource(resource); + + if (timer->state.timestamp_nsec) { + wl_resource_post_error(resource, + WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, + "surface already has a timestamp"); + return; + } + + uint64_t tv_sec = (uint64_t)tv_sec_hi<<32 | tv_sec_lo; + // check for overflow + if (tv_nsec >= NSEC_PER_SEC || + tv_sec > (UINT64_MAX - tv_nsec) / NSEC_PER_SEC) { + wl_resource_post_error(resource, + WP_COMMIT_TIMER_V1_ERROR_INVALID_TIMESTAMP, + "invalid timestamp"); + return; + } + + timer->state.timestamp_nsec = tv_sec * NSEC_PER_SEC + tv_nsec; +} + +static void timer_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_commit_timer_v1_interface timer_impl = { + .destroy = timer_handle_destroy, + .set_timestamp = timer_handle_set_timestamp +}; + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_commit_timer_v1 *timer = wl_container_of(addon, timer, addon); + wl_resource_destroy(timer->resource); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wp_commit_timer_v1", + .destroy = surface_addon_destroy, +}; + +static void timer_reset(struct wlr_commit_timer_v1 *timer) { + struct wlr_commit_timer_v1_commit *commit, *tmp_co; + wl_list_for_each_safe(commit, tmp_co, &timer->commits, link) { + commit_destroy(commit); + } + if (timer->output) { + timer->output_present.notify = NULL; + wl_list_remove(&timer->output_present.link); + timer->output_destroy.notify = NULL; + wl_list_remove(&timer->output_destroy.link); + timer->output = NULL; + } + memset(&timer->state, 0, sizeof(timer->state)); +} + +static void timer_handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_commit_timer_v1 *timer = + wl_container_of(listener, timer, output_destroy); + timer_reset(timer); +} + +static void timer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_commit_timer_v1 *timer = wlr_commit_timer_v1_from_resource(resource); + timer_reset(timer); + wlr_addon_finish(&timer->addon); + wl_list_remove(&timer->client_commit.link); + wl_signal_emit_mutable(&timer->events.destroy, timer); + free(timer); +} + +static struct wlr_commit_timer_v1 *commit_timer_create(struct wl_client *wl_client, uint32_t version, + uint32_t id, struct wlr_surface *surface) { + struct wlr_commit_timer_v1 *timer = calloc(1, sizeof(*timer)); + if (timer == NULL) { + goto err_alloc; + } + + timer->resource = wl_resource_create(wl_client, &wp_commit_timer_v1_interface, version, id); + if (timer->resource == NULL) { + goto err_alloc; + } + wl_resource_set_implementation(timer->resource, &timer_impl, timer, + timer_handle_resource_destroy); + + wl_list_init(&timer->commits); + + /* we will use the wl_display to add a timer to the wl_event_loop */ + timer->wl_display = wl_client_get_display(wl_client); + + timer->surface = surface; + timer->client_commit.notify = timer_handle_client_commit; + wl_signal_add(&timer->surface->events.client_commit, &timer->client_commit); + + wlr_log(WLR_DEBUG, "New wlr_commit_timer_v1 %p (res %p)", timer, timer->resource); + + return timer; + +err_alloc: + free(timer); + return NULL; +} + +static const struct wp_commit_timing_manager_v1_interface timing_manager_impl; +static struct wlr_commit_timing_manager_v1 *wlr_commit_timing_manager_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_commit_timing_manager_v1_interface, + &timing_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void timing_manager_handle_get_timer(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + if (wlr_addon_find(&surface->addons, NULL, &surface_addon_impl) != NULL) { + wl_resource_post_error(resource, + WP_COMMIT_TIMING_MANAGER_V1_ERROR_COMMIT_TIMER_EXISTS, + "A wp_commit_timer_v1 object already exists for this surface"); + return; + } + + struct wlr_commit_timer_v1 *timer = + commit_timer_create(wl_client, wl_resource_get_version(resource), id, surface); + if (!timer) { + wl_client_post_no_memory(wl_client); + return; + } + + wlr_addon_init(&timer->addon, &surface->addons, NULL, &surface_addon_impl); + wl_signal_init(&timer->events.destroy); + + struct wlr_commit_timing_manager_v1 *manager = + wlr_commit_timing_manager_v1_from_resource(resource); + + timer->timing_manager = manager; + + // it is possible that at this time we have no outputs assigned to the surface yet + struct wlr_surface_output *surface_output = NULL; + if (!wl_list_empty(&surface->current_outputs)) { + wl_list_for_each(surface_output, &surface->current_outputs, link) { + break; + } + } + wlr_commit_timer_v1_set_output(timer, surface_output ? surface_output->output : NULL); + + wl_signal_emit_mutable(&timer->timing_manager->events.new_timer, + &(struct wlr_commit_timing_manager_v1_new_timer_event){.timer = timer}); +} + +static void timing_manager_handle_destroy(struct wl_client *wl_client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_commit_timing_manager_v1_interface timing_manager_impl = { + .get_timer = timing_manager_handle_get_timer, + .destroy = timing_manager_handle_destroy, +}; + +static void timing_manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wl_resource *resource = + wl_resource_create(client, &wp_commit_timing_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_commit_timing_manager_v1 *manager = data; + wl_resource_set_implementation(resource, &timing_manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_commit_timing_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_commit_timing_manager_v1 *wlr_commit_timing_manager_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= TIMING_MANAGER_VERSION); + + struct wlr_commit_timing_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + goto err_out; + } + + manager->global = wl_global_create(display, &wp_commit_timing_manager_v1_interface, + version, manager, timing_manager_bind); + if (!manager->global) { + goto err_out; + } + + wl_signal_init(&manager->events.new_timer); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; + +err_out: + free(manager); + return NULL; +} + +void wlr_commit_timer_v1_set_output(struct wlr_commit_timer_v1 *timer, + struct wlr_output *output) { + timer_reset(timer); + + if (!output) { + return; + } + + timer->output = output; + // we make a copy of the refresh rate so that we can check for whenever it changes + timer->state.refresh = output->refresh; + + timer->output_present.notify = timer_handle_output_present; + wl_signal_add(&output->events.present, &timer->output_present); + timer->output_destroy.notify = timer_handle_output_destroy; + wl_signal_add(&output->events.destroy, &timer->output_destroy); +} From 52b6316e9c0192493e6a7e063f771d5ce1aaf872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20G=C3=B3mez?= Date: Wed, 26 Nov 2025 10:35:20 -0500 Subject: [PATCH 3/3] commit-timing-v1: add scene implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergio Gómez --- include/wlr/types/wlr_scene.h | 11 ++++++ types/scene/surface.c | 9 +++++ types/scene/wlr_scene.c | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 2363c93ce..013d100a1 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -104,12 +104,17 @@ struct wlr_scene { struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; struct wlr_color_manager_v1 *color_manager_v1; + struct wlr_commit_timing_manager_v1 *commit_timing_manager_v1; + struct wl_list commit_timers; // wlr_commit_timer_v1.scene_link struct { struct wl_listener linux_dmabuf_v1_destroy; struct wl_listener gamma_control_manager_v1_destroy; struct wl_listener gamma_control_manager_v1_set_gamma; struct wl_listener color_manager_v1_destroy; + struct wl_listener commit_timing_manager_v1_destroy; + struct wl_listener commit_timing_manager_v1_new_timer; + struct wl_listener commit_timer_v1_destroy; enum wlr_scene_debug_damage_option debug_damage_option; bool direct_scanout; @@ -383,6 +388,12 @@ void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, */ void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_manager_v1 *manager); +/** + * Handles commit_timing_manager_v1 for all surfaces and their primary outputs in the scene. + */ +void wlr_scene_set_commit_timing_manager_v1(struct wlr_scene *scene, + struct wlr_commit_timing_manager_v1 *timing_manager); + /** * Add a node displaying nothing but its children. */ diff --git a/types/scene/surface.c b/types/scene/surface.c index 0c24208cb..068b34d7a 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,14 @@ static void handle_scene_buffer_outputs_update( wlr_color_manager_v1_set_surface_preferred_image_description(scene->color_manager_v1, surface->surface, &img_desc); } + + struct wlr_commit_timer_v1 *timer; + wl_list_for_each(timer, &scene->commit_timers, scene_link) { + if (timer->surface == surface->surface && surface->buffer->active_outputs && + timer->output != surface->buffer->primary_output->output) { + wlr_commit_timer_v1_set_output(timer, surface->buffer->primary_output->output); + } + } } static void handle_scene_buffer_output_enter( diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index cb374258d..c7caa73b5 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +182,7 @@ struct wlr_scene *wlr_scene_create(void) { scene_tree_init(&scene->tree, NULL); wl_list_init(&scene->outputs); + wl_list_init(&scene->commit_timers); wl_list_init(&scene->linux_dmabuf_v1_destroy.link); wl_list_init(&scene->gamma_control_manager_v1_destroy.link); wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); @@ -1604,6 +1606,78 @@ void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_ma wl_signal_add(&manager->events.destroy, &scene->color_manager_v1_destroy); } +static void scene_handle_commit_timer_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, commit_timer_v1_destroy); + struct wlr_commit_timer_v1 *timer = data; + struct wlr_surface *surface = timer->surface; + + struct wlr_commit_timer_v1 *tmp_timer; + wl_list_for_each_safe(timer, tmp_timer, &scene->commit_timers, scene_link) { + if (timer->surface == surface) { + wl_list_remove(&timer->scene_link); + } + } +} + +static void timer_set_output(struct wlr_scene_buffer *scene_buffer, int x, int y, void *data) { + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (!scene_surface) { + return; + } + + struct wlr_commit_timer_v1 *timer = data; + if (scene_surface->surface == timer->surface) { + struct wlr_scene_output *primary_output = scene_surface->buffer->primary_output; + if (primary_output) { + wlr_commit_timer_v1_set_output(timer, primary_output->output); + } + } +} + +static void scene_handle_commit_timing_manager_v1_new_timer(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, commit_timing_manager_v1_new_timer); + struct wlr_commit_timing_manager_v1_new_timer_event *event = data; + struct wlr_commit_timer_v1 *timer = event->timer; + + wl_list_insert(&scene->commit_timers, &timer->scene_link); + scene->commit_timer_v1_destroy.notify = scene_handle_commit_timer_v1_destroy; + wl_signal_add(&timer->events.destroy, &scene->commit_timer_v1_destroy); + + wlr_scene_node_for_each_buffer(&scene->tree.node, timer_set_output, event->timer); +} + +static void scene_handle_commit_timing_manager_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, commit_timing_manager_v1_destroy); + wl_list_remove(&scene->commit_timing_manager_v1_destroy.link); + wl_list_init(&scene->commit_timing_manager_v1_destroy.link); + wl_list_remove(&scene->commit_timing_manager_v1_new_timer.link); + wl_list_init(&scene->commit_timing_manager_v1_new_timer.link); + scene->commit_timing_manager_v1 = NULL; +} + +void wlr_scene_set_commit_timing_manager_v1(struct wlr_scene *scene, + struct wlr_commit_timing_manager_v1 *commit_timing) { + assert(scene->commit_timing_manager_v1 == NULL); + + scene->commit_timing_manager_v1 = commit_timing; + + scene->commit_timing_manager_v1_new_timer.notify = + scene_handle_commit_timing_manager_v1_new_timer; + wl_signal_add(&commit_timing->events.new_timer, + &scene->commit_timing_manager_v1_new_timer); + scene->commit_timing_manager_v1_destroy.notify = + scene_handle_commit_timing_manager_v1_destroy; + wl_signal_add(&commit_timing->events.destroy, + &scene->commit_timing_manager_v1_destroy); +} + static void scene_output_handle_destroy(struct wlr_addon *addon) { struct wlr_scene_output *scene_output = wl_container_of(addon, scene_output, addon);