From 4bb81a4c98b3c2e54ed2dd5ae681ee29c8df13e8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 17 Nov 2019 00:34:19 +0100 Subject: [PATCH 1/4] output: set wlr_output.commit_seq before firing the commit event This allows listeners to read the commit sequence number. --- types/wlr_output.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/wlr_output.c b/types/wlr_output.c index 793907dd92..3e397532b5 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -495,13 +495,14 @@ bool wlr_output_commit(struct wlr_output *output) { wlr_surface_send_frame_done(cursor->surface, &now); } + output->commit_seq++; + wlr_signal_emit_safe(&output->events.commit, output); output->frame_pending = true; output->needs_frame = false; output_state_clear(&output->pending); pixman_region32_clear(&output->damage); - output->commit_seq++; return true; } From ce3ac647817023ce4659e2b7ba65da375b916a28 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 17 Nov 2019 00:56:27 +0100 Subject: [PATCH 2/4] output: fix off-by-one wlr_output_event_present.commit_seq Backends not supporting presentation feedback call wlr_output_send_present with a NULL event in their commit handler. Since the commit hasn't been applied yet, commit_seq still has its old value. We need to increment it. An alternative would be to move commit_seq in wlr_output_state. This would allow to have a pending and a current commit_seq. wlr_output_send_present could take the pending commit_seq when called with a NULL event. --- types/wlr_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_output.c b/types/wlr_output.c index 3e397532b5..bf29ff9c25 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -570,7 +570,7 @@ void wlr_output_send_present(struct wlr_output *output, struct wlr_output_event_present _event = {0}; if (event == NULL) { event = &_event; - event->commit_seq = output->commit_seq; + event->commit_seq = output->commit_seq + 1; } event->output = output; From 6718e6854aee8258045a0cde4973a2b661ddbd07 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 16 Nov 2019 23:44:28 +0100 Subject: [PATCH 3/4] presentation-time: make API more flexible The wlr_presentation_feedback struct now tracks presentation feedback for multiple resources (but still a single surface content update). This allows the compositor to properly send presentation events even when there is more than one frame of latency or when it references a surface's buffer. --- include/wlr/types/wlr_presentation_time.h | 29 +++- types/wlr_presentation_time.c | 161 +++++++++++++--------- 2 files changed, 120 insertions(+), 70 deletions(-) diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h index bf1d3ac25f..f25bd1a104 100644 --- a/include/wlr/types/wlr_presentation_time.h +++ b/include/wlr/types/wlr_presentation_time.h @@ -28,17 +28,19 @@ struct wlr_presentation { }; struct wlr_presentation_feedback { - struct wl_resource *resource; struct wlr_presentation *presentation; - struct wlr_surface *surface; + struct wlr_surface *surface; // NULL if the surface has been destroyed struct wl_list link; // wlr_presentation::feedbacks + struct wl_list resources; // wl_resource_get_link + // The surface contents were committed. bool committed; // The surface contents were sampled by the compositor and are to be // presented on the next flip. Can become true only after committed becomes // true. bool sampled; + bool presented; struct wl_listener surface_commit; struct wl_listener surface_destroy; @@ -58,10 +60,25 @@ struct wlr_backend; struct wlr_presentation *wlr_presentation_create(struct wl_display *display, struct wlr_backend *backend); void wlr_presentation_destroy(struct wlr_presentation *presentation); -void wlr_presentation_send_surface_presented( - struct wlr_presentation *presentation, struct wlr_surface *surface, - struct wlr_presentation_event *event); -void wlr_presentation_surface_sampled( +/** + * Mark the current surface's buffer as sampled. + * + * The compositor must call this function when it uses the surface's current + * contents (e.g. when rendering the surface's current texture, when + * referencing its current buffer, or when directly scanning out its current + * buffer). A wlr_presentation_feedback is returned. The compositor should call + * wlr_presentation_feedback_send_presented if this content has been displayed, + * then wlr_presentation_feedback_destroy. + * + * NULL is returned if the client hasn't requested presentation feedback for + * this surface. + */ +struct wlr_presentation_feedback *wlr_presentation_surface_sampled( struct wlr_presentation *presentation, struct wlr_surface *surface); +void wlr_presentation_feedback_send_presented( + struct wlr_presentation_feedback *feedback, + struct wlr_presentation_event *event); +void wlr_presentation_feedback_destroy( + struct wlr_presentation_feedback *feedback); #endif diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c index 3c49e2fbcc..f0b8965e97 100644 --- a/types/wlr_presentation_time.c +++ b/types/wlr_presentation_time.c @@ -9,31 +9,19 @@ #define PRESENTATION_VERSION 1 -static struct wlr_presentation_feedback *presentation_feedback_from_resource( - struct wl_resource *resource) { - assert(wl_resource_instance_of(resource, - &wp_presentation_feedback_interface, NULL)); - return wl_resource_get_user_data(resource); -} - static void feedback_handle_resource_destroy(struct wl_resource *resource) { - struct wlr_presentation_feedback *feedback = - presentation_feedback_from_resource(resource); - wl_list_remove(&feedback->surface_commit.link); - wl_list_remove(&feedback->surface_destroy.link); - wl_list_remove(&feedback->link); - free(feedback); + wl_list_remove(wl_resource_get_link(resource)); } -// Destroys the feedback -static void feedback_send_presented(struct wlr_presentation_feedback *feedback, +static void feedback_resource_send_presented( + struct wl_resource *feedback_resource, struct wlr_presentation_event *event) { - struct wl_client *client = wl_resource_get_client(feedback->resource); - struct wl_resource *resource; - wl_resource_for_each(resource, &event->output->resources) { - if (wl_resource_get_client(resource) == client) { - wp_presentation_feedback_send_sync_output(feedback->resource, - resource); + struct wl_client *client = wl_resource_get_client(feedback_resource); + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, &event->output->resources) { + if (wl_resource_get_client(output_resource) == client) { + wp_presentation_feedback_send_sync_output(feedback_resource, + output_resource); } } @@ -41,18 +29,17 @@ static void feedback_send_presented(struct wlr_presentation_feedback *feedback, uint32_t tv_sec_lo = event->tv_sec & 0xFFFFFFFF; uint32_t seq_hi = event->seq >> 32; uint32_t seq_lo = event->seq & 0xFFFFFFFF; - wp_presentation_feedback_send_presented(feedback->resource, + wp_presentation_feedback_send_presented(feedback_resource, tv_sec_hi, tv_sec_lo, event->tv_nsec, event->refresh, seq_hi, seq_lo, event->flags); - wl_resource_destroy(feedback->resource); + wl_resource_destroy(feedback_resource); } -// Destroys the feedback -static void feedback_send_discarded( - struct wlr_presentation_feedback *feedback) { - wp_presentation_feedback_send_discarded(feedback->resource); - wl_resource_destroy(feedback->resource); +static void feedback_resource_send_discarded( + struct wl_resource *feedback_resource) { + wp_presentation_feedback_send_discarded(feedback_resource); + wl_resource_destroy(feedback_resource); } static void feedback_handle_surface_commit(struct wl_listener *listener, @@ -63,18 +50,33 @@ static void feedback_handle_surface_commit(struct wl_listener *listener, if (feedback->committed) { if (!feedback->sampled) { // The content update has been superseded - feedback_send_discarded(feedback); + wlr_presentation_feedback_destroy(feedback); } } else { feedback->committed = true; } } +static void feedback_unset_surface(struct wlr_presentation_feedback *feedback) { + if (feedback->surface == NULL) { + return; + } + + feedback->surface = NULL; + wl_list_remove(&feedback->surface_commit.link); + wl_list_remove(&feedback->surface_destroy.link); +} + static void feedback_handle_surface_destroy(struct wl_listener *listener, void *data) { struct wlr_presentation_feedback *feedback = wl_container_of(listener, feedback, surface_destroy); - feedback_send_discarded(feedback); + if (feedback->sampled) { + // The compositor might have a handle on this feedback + feedback_unset_surface(feedback); + } else { + wlr_presentation_feedback_destroy(feedback); + } } static const struct wp_presentation_interface presentation_impl; @@ -87,39 +89,50 @@ static struct wlr_presentation *presentation_from_resource( } static void presentation_handle_feedback(struct wl_client *client, - struct wl_resource *resource, struct wl_resource *surface_resource, - uint32_t id) { + struct wl_resource *presentation_resource, + struct wl_resource *surface_resource, uint32_t id) { struct wlr_presentation *presentation = - presentation_from_resource(resource); + presentation_from_resource(presentation_resource); struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); - struct wlr_presentation_feedback *feedback = - calloc(1, sizeof(struct wlr_presentation_feedback)); - if (feedback == NULL) { - wl_client_post_no_memory(client); - return; + bool found = false; + struct wlr_presentation_feedback *feedback; + wl_list_for_each(feedback, &presentation->feedbacks, link) { + if (feedback->surface == surface && !feedback->committed) { + found = true; + break; + } + } + if (!found) { + feedback = calloc(1, sizeof(struct wlr_presentation_feedback)); + if (feedback == NULL) { + wl_client_post_no_memory(client); + return; + } + + feedback->surface = surface; + wl_list_init(&feedback->resources); + + feedback->surface_commit.notify = feedback_handle_surface_commit; + wl_signal_add(&surface->events.commit, &feedback->surface_commit); + + feedback->surface_destroy.notify = feedback_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &feedback->surface_destroy); + + wl_list_insert(&presentation->feedbacks, &feedback->link); } - uint32_t version = wl_resource_get_version(resource); - feedback->resource = wl_resource_create(client, + uint32_t version = wl_resource_get_version(presentation_resource); + struct wl_resource *resource = wl_resource_create(client, &wp_presentation_feedback_interface, version, id); - if (feedback->resource == NULL) { - free(feedback); + if (resource == NULL) { wl_client_post_no_memory(client); return; } - wl_resource_set_implementation(feedback->resource, NULL, feedback, + wl_resource_set_implementation(resource, NULL, feedback, feedback_handle_resource_destroy); - feedback->surface = surface; - - feedback->surface_commit.notify = feedback_handle_surface_commit; - wl_signal_add(&surface->events.commit, &feedback->surface_commit); - - feedback->surface_destroy.notify = feedback_handle_surface_destroy; - wl_signal_add(&surface->events.destroy, &feedback->surface_destroy); - - wl_list_insert(&presentation->feedbacks, &feedback->link); + wl_list_insert(&feedback->resources, wl_resource_get_link(resource)); } static void presentation_handle_destroy(struct wl_client *client, @@ -198,7 +211,7 @@ void wlr_presentation_destroy(struct wlr_presentation *presentation) { struct wlr_presentation_feedback *feedback, *feedback_tmp; wl_list_for_each_safe(feedback, feedback_tmp, &presentation->feedbacks, link) { - wl_resource_destroy(feedback->resource); + wlr_presentation_feedback_destroy(feedback); } struct wl_resource *resource, *resource_tmp; @@ -211,27 +224,47 @@ void wlr_presentation_destroy(struct wlr_presentation *presentation) { free(presentation); } -void wlr_presentation_send_surface_presented( - struct wlr_presentation *presentation, struct wlr_surface *surface, +void wlr_presentation_feedback_send_presented( + struct wlr_presentation_feedback *feedback, struct wlr_presentation_event *event) { - // TODO: maybe use a hashtable to optimize this function - struct wlr_presentation_feedback *feedback, *feedback_tmp; - wl_list_for_each_safe(feedback, feedback_tmp, - &presentation->feedbacks, link) { - if (feedback->surface == surface && feedback->sampled) { - feedback_send_presented(feedback, event); - } + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &feedback->resources) { + feedback_resource_send_presented(resource, event); } + + feedback->presented = true; } -void wlr_presentation_surface_sampled( +struct wlr_presentation_feedback *wlr_presentation_surface_sampled( struct wlr_presentation *presentation, struct wlr_surface *surface) { // TODO: maybe use a hashtable to optimize this function struct wlr_presentation_feedback *feedback, *feedback_tmp; wl_list_for_each_safe(feedback, feedback_tmp, &presentation->feedbacks, link) { - if (feedback->surface == surface && feedback->committed) { + if (feedback->surface == surface && feedback->committed && + !feedback->sampled) { feedback->sampled = true; + return feedback; } } + return NULL; +} + +void wlr_presentation_feedback_destroy( + struct wlr_presentation_feedback *feedback) { + if (feedback == NULL) { + return; + } + + if (!feedback->presented) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &feedback->resources) { + feedback_resource_send_discarded(resource); + } + } + assert(wl_list_empty(&feedback->resources)); + + feedback_unset_surface(feedback); + wl_list_remove(&feedback->link); + free(feedback); } From 34b3526c2ea04d9e5581292c27614491d8b771e7 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 17 Nov 2019 00:20:26 +0100 Subject: [PATCH 4/4] presentation-time: add helper for common case Most of the time, compositors just display the surface's current buffer on an output. Add an helper to make it easy to support presentation-time in this case. --- include/wlr/types/wlr_presentation_time.h | 30 +++++++++ types/wlr_presentation_time.c | 81 +++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h index f25bd1a104..9a7e6e7034 100644 --- a/include/wlr/types/wlr_presentation_time.h +++ b/include/wlr/types/wlr_presentation_time.h @@ -14,6 +14,9 @@ #include #include +struct wlr_output; +struct wlr_output_event_present; + struct wlr_presentation { struct wl_global *global; struct wl_list resources; // wl_resource_get_link @@ -42,8 +45,17 @@ struct wlr_presentation_feedback { bool sampled; bool presented; + // Only when the wlr_presentation_surface_sampled_on_output helper has been + // called + struct wlr_output *output; + bool output_committed; + uint32_t output_commit_seq; + struct wl_listener surface_commit; struct wl_listener surface_destroy; + struct wl_listener output_commit; + struct wl_listener output_present; + struct wl_listener output_destroy; }; struct wlr_presentation_event { @@ -81,4 +93,22 @@ void wlr_presentation_feedback_send_presented( void wlr_presentation_feedback_destroy( struct wlr_presentation_feedback *feedback); +/** + * Fill a wlr_presentation_event from a wlr_output_event_present. + */ +void wlr_presentation_event_from_output(struct wlr_presentation_event *event, + const struct wlr_output_event_present *output_event); + +/** + * Mark the current surface's buffer as sampled on the given output. + * + * Instead of calling wlr_presentation_surface_sampled and managing the + * wlr_presentation_feedback itself, the compositor can call this function + * before a wlr_output_commit call to indicate that the surface's current + * contents will be displayed on the output. + */ +void wlr_presentation_surface_sampled_on_output( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_output *output); + #endif diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c index f0b8965e97..97d24042c1 100644 --- a/types/wlr_presentation_time.c +++ b/types/wlr_presentation_time.c @@ -1,6 +1,7 @@ #define _POSIX_C_SOURCE 199309L #include #include +#include #include #include #include @@ -250,6 +251,8 @@ struct wlr_presentation_feedback *wlr_presentation_surface_sampled( return NULL; } +static void feedback_unset_output(struct wlr_presentation_feedback *feedback); + void wlr_presentation_feedback_destroy( struct wlr_presentation_feedback *feedback) { if (feedback == NULL) { @@ -265,6 +268,84 @@ void wlr_presentation_feedback_destroy( assert(wl_list_empty(&feedback->resources)); feedback_unset_surface(feedback); + feedback_unset_output(feedback); wl_list_remove(&feedback->link); free(feedback); } + +void wlr_presentation_event_from_output(struct wlr_presentation_event *event, + const struct wlr_output_event_present *output_event) { + memset(event, 0, sizeof(*event)); + event->output = output_event->output; + event->tv_sec = (uint64_t)output_event->when->tv_sec; + event->tv_nsec = (uint32_t)output_event->when->tv_nsec; + event->refresh = (uint32_t)output_event->refresh; + event->seq = (uint64_t)output_event->seq; + event->flags = output_event->flags; +} + +static void feedback_unset_output(struct wlr_presentation_feedback *feedback) { + if (feedback->output == NULL) { + return; + } + + feedback->output = NULL; + wl_list_remove(&feedback->output_commit.link); + wl_list_remove(&feedback->output_present.link); + wl_list_remove(&feedback->output_destroy.link); +} + +static void feedback_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_commit); + if (feedback->output_committed) { + return; + } + feedback->output_committed = true; + feedback->output_commit_seq = feedback->output->commit_seq; +} + +static void feedback_handle_output_present(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_present); + struct wlr_output_event_present *output_event = data; + + if (!feedback->output_committed || + output_event->commit_seq != feedback->output_commit_seq) { + return; + } + + struct wlr_presentation_event event = {0}; + wlr_presentation_event_from_output(&event, output_event); + wlr_presentation_feedback_send_presented(feedback, &event); + wlr_presentation_feedback_destroy(feedback); +} + +static void feedback_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, output_destroy); + wlr_presentation_feedback_destroy(feedback); +} + +void wlr_presentation_surface_sampled_on_output( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_output *output) { + struct wlr_presentation_feedback *feedback = + wlr_presentation_surface_sampled(presentation, surface); + if (feedback == NULL) { + return; + } + + assert(feedback->output == NULL); + feedback->output = output; + + feedback->output_commit.notify = feedback_handle_output_commit; + wl_signal_add(&output->events.commit, &feedback->output_commit); + feedback->output_present.notify = feedback_handle_output_present; + wl_signal_add(&output->events.present, &feedback->output_present); + feedback->output_destroy.notify = feedback_handle_output_destroy; + wl_signal_add(&output->events.destroy, &feedback->output_destroy); +}