[RFCv3 weston 12/15] compositor: implement presentation.queue

Pekka Paalanen ppaalanen at gmail.com
Fri Mar 7 04:04:00 PST 2014


From: Pekka Paalanen <pekka.paalanen at collabora.co.uk>

Implement the queueing and queue processing of the Presentation
extension.

Every weston_surface (wl_surface) has a queue_list, which is ordered by
the target presentation timestamp of the queued updates. A
wl_surface.commit following a presentation.queue will trigger queueing
instead of a normal commit.

The buffer_viewport handling is changed to match the Presentation
specification.

In output repaint, the compositor predicts when the current repaint
would hit the screen, based on the previous repaint's timestamp.
The DRM vblank events carry a timestamp for the start of the active
period, but they can be emitted earlier. This can make it look like the
previous presentation finished in the future. This is dealt with by
using proper rounding, and allowing timestamps that are at most 1 cycle
in the future in weston_output_predict_presentation.

The weston_surface::queue_list is processed during output repaint if the
weston_surface's main output is the one being repainted. An update from
the queue is applied according to the target timestamp, and earlier
updates are discarded.

The compositor will keep on repainting if there are any queued updates
still existing in the weston_surfaces that have been processed by output
repaint. Surfaces that are not part of the repaint will not
automatically schedule a new repaint based on the queue; instead it is
assumed that a repaint is scheduled when such a surface enters the
repainted set of surfaces. A client queueing an update will also cause a
repaint to be scheduled, to ensure the queue will get processed if the
surface is on an output.

As the realized presentation timestamp is available only after output
repaint, updating all relevant surface timestamps would require a
per-output list of weston_surfaces. Instead, weston_surfaces hold a
reference to a weston_timestamp, which then gets the realized
presentation timestamp in finish_frame(). The surface's
presentation_time is used to discard queued updates that would replace
the current content with an older content.

Frame callbacks are not queued. A queueing commit does not touch any
pending frame callbacks.

Signed-off-by: Pekka Paalanen <pekka.paalanen at collabora.co.uk>
---
 src/compositor.c | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/compositor.h |  18 +++
 2 files changed, 426 insertions(+), 28 deletions(-)

diff --git a/src/compositor.c b/src/compositor.c
index c05167d..00213d4 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -344,6 +344,58 @@ region_init_infinite(pixman_region32_t *region)
 				  UINT32_MAX, UINT32_MAX);
 }
 
+struct weston_timestamp {
+	int ref_count;
+	struct timespec stamp;
+};
+
+static struct weston_timestamp *
+weston_timestamp_ref(struct weston_timestamp *ts)
+{
+	++ts->ref_count;
+	return ts;
+}
+
+static struct weston_timestamp *
+weston_timestamp_create(void)
+{
+	struct weston_timestamp *ts;
+
+	ts = malloc(sizeof *ts);
+	if (!ts)
+		return NULL;
+
+	ts->ref_count = 1;
+	ts->stamp.tv_sec = 0;
+	ts->stamp.tv_nsec = -1;
+
+	return ts;
+}
+
+static void
+weston_timestamp_unref(struct weston_timestamp *ts)
+{
+	if (!ts)
+		return;
+
+	if (--ts->ref_count)
+		return;
+
+	free(ts);
+}
+
+static const struct timespec *
+weston_timestamp_get_time(const struct weston_timestamp *ts)
+{
+	if (!ts)
+		return NULL;
+
+	if (ts->stamp.tv_nsec < 0)
+		return NULL;
+
+	return &ts->stamp;
+}
+
 static struct weston_subsurface *
 weston_surface_to_subsurface(struct weston_surface *surface);
 
@@ -427,10 +479,13 @@ weston_surface_create(struct weston_compositor *compositor)
 	region_init_infinite(&surface->pending.input);
 	wl_list_init(&surface->pending.frame_callback_list);
 	wl_list_init(&surface->pending.feedback_list);
+	surface->pending.target_timestamp.tv_nsec = -1;
 
 	wl_list_init(&surface->subsurface_list);
 	wl_list_init(&surface->subsurface_list_pending);
 
+	wl_list_init(&surface->queue_list);
+
 	return surface;
 }
 
@@ -1404,6 +1459,182 @@ weston_presentation_feedback_present_list(struct wl_list *list,
 						     refresh_nsec, ts, seq);
 }
 
+struct weston_queued_update {
+	struct wl_list queue_link;
+	struct timespec timestamp;
+	struct weston_buffer_reference buffer_ref;
+	struct wl_list feedback_list;
+	struct weston_buffer_viewport buffer_viewport;
+};
+
+#define NSEC_PER_SEC 1000000000
+#define ONE_PER_PICO 1000000000000
+
+static int
+timespec_cmp(const struct timespec *a, const struct timespec *b)
+{
+	assert(a->tv_nsec >= 0 && a->tv_nsec < NSEC_PER_SEC);
+	assert(b->tv_nsec >= 0 && b->tv_nsec < NSEC_PER_SEC);
+
+	if (a->tv_sec < b->tv_sec)
+		return -1;
+
+	if (a->tv_sec > b->tv_sec)
+		return 1;
+
+	if (a->tv_nsec < b->tv_nsec)
+		return -1;
+
+	if (a->tv_nsec > b->tv_nsec)
+		return 1;
+
+	return 0;
+}
+
+static void
+weston_surface_queue_insert_update(struct weston_surface *surface,
+				   struct weston_queued_update *update)
+{
+	struct weston_queued_update *pos = NULL;
+	struct weston_queued_update *tmp;
+
+	/* Optimal for adding in increasing timestamp order,
+	 * worst case for adding in decreasing timestamp order. */
+	wl_list_for_each_reverse(tmp, &surface->queue_list, queue_link) {
+		if (timespec_cmp(&update->timestamp, &tmp->timestamp) >= 0) {
+			pos = tmp;
+			break;
+		}
+	}
+
+	if (pos)
+		wl_list_insert(&pos->queue_link, &update->queue_link);
+	else
+		wl_list_insert(&surface->queue_list, &update->queue_link);
+}
+
+static void
+weston_surface_queue_update(struct weston_surface *surface)
+{
+	struct weston_queued_update *update;
+
+	assert(surface->resource);
+
+	update = calloc(1, sizeof *update);
+	if (!update) {
+		wl_resource_post_no_memory(surface->resource);
+		return;
+	}
+
+	update->timestamp = surface->pending.target_timestamp;
+	weston_buffer_reference(&update->buffer_ref, surface->pending.buffer);
+	update->buffer_viewport.buffer =
+		surface->pending.buffer_viewport.buffer;
+
+	wl_list_init(&update->feedback_list);
+	wl_list_insert_list(&update->feedback_list,
+			    &surface->pending.feedback_list);
+	wl_list_init(&surface->pending.feedback_list);
+
+	weston_surface_reset_pending_buffer(surface);
+	surface->pending.target_timestamp.tv_nsec = -1;
+
+	weston_surface_queue_insert_update(surface, update);
+
+	weston_surface_schedule_repaint(surface);
+}
+
+static void
+weston_surface_attach(struct weston_surface *surface,
+		      struct weston_buffer *buffer);
+
+static void
+weston_surface_commit_queued_update_destroy(struct weston_surface *surface,
+					    struct weston_queued_update *update)
+{
+	/* Intentionally ignore weston_subsurface::synchronized. */
+
+	surface->buffer_viewport.buffer = update->buffer_viewport.buffer;
+
+	weston_surface_attach(surface, update->buffer_ref.buffer);
+
+	if (surface->configure)
+		surface->configure(surface, 0, 0);
+
+	pixman_region32_fini(&surface->damage);
+	pixman_region32_init_rect(&surface->damage, 0, 0,
+				  surface->width, surface->height);
+
+	wl_list_insert_list(&surface->feedback_list, &update->feedback_list);
+
+	weston_buffer_reference(&update->buffer_ref, NULL);
+	wl_list_remove(&update->queue_link);
+	free(update);
+	/* Do not schedule a repaint, because we are called from repaint. */
+}
+
+static void
+weston_queued_update_destroy(struct weston_queued_update *update)
+{
+	wl_list_remove(&update->queue_link);
+	weston_buffer_reference(&update->buffer_ref, NULL);
+	weston_presentation_feedback_discard_list(&update->feedback_list);
+	free(update);
+}
+
+static int
+weston_surface_queue_process(struct weston_surface *surface,
+			     const struct timespec *target,
+			     const struct timespec *cutoff)
+{
+	struct weston_queued_update *update = NULL;
+	struct weston_queued_update *pos;
+	const struct timespec *current;
+
+	wl_list_for_each(pos, &surface->queue_list, queue_link) {
+		if (timespec_cmp(&pos->timestamp, cutoff) >= 0)
+			break;
+
+		if (update)
+			weston_queued_update_destroy(update);
+
+		update = pos;
+	}
+
+	if (!update)
+		goto out;
+
+	/*
+	 * If the surface had no content yet, we should accept the chosen
+	 * update from the queue always. The below would discard it if
+	 * the update timestamp was before 'target'. However, the surface
+	 * always has content, because we get here from repaint, which
+	 * does not process surfaces unless they are mapped.
+	 */
+
+	current = weston_timestamp_get_time(surface->presentation_time);
+	if (!current)
+		current = target;
+
+	if (timespec_cmp(&update->timestamp, current) < 0)
+		weston_queued_update_destroy(update);
+	else
+		weston_surface_commit_queued_update_destroy(surface, update);
+
+out:
+	/* Return true, if there are more updates still queued. */
+	return !wl_list_empty(&surface->queue_list);
+}
+
+static void
+weston_surface_discard_queue(struct weston_surface *surface)
+{
+	struct weston_queued_update *update, *tmp;
+
+	wl_list_for_each_safe(update, tmp, &surface->queue_list, queue_link)
+		weston_queued_update_destroy(update);
+}
+
 WL_EXPORT void
 weston_view_destroy(struct weston_view *view)
 {
@@ -1440,6 +1671,8 @@ weston_surface_destroy(struct weston_surface *surface)
 
 	wl_signal_emit(&surface->destroy_signal, &surface->resource);
 
+	weston_surface_discard_queue(surface);
+
 	assert(wl_list_empty(&surface->subsurface_list_pending));
 	assert(wl_list_empty(&surface->subsurface_list));
 
@@ -1470,6 +1703,7 @@ weston_surface_destroy(struct weston_surface *surface)
 		wl_resource_destroy(cb->resource);
 
 	weston_presentation_feedback_discard_list(&surface->feedback_list);
+	weston_timestamp_unref(surface->presentation_time);
 
 	free(surface);
 }
@@ -1563,6 +1797,8 @@ weston_surface_attach(struct weston_surface *surface,
 		      struct weston_buffer *buffer)
 {
 	weston_buffer_reference(&surface->buffer_ref, buffer);
+	weston_timestamp_unref(surface->presentation_time);
+	surface->presentation_time = NULL;
 
 	if (!buffer) {
 		if (weston_surface_is_mapped(surface))
@@ -1806,6 +2042,63 @@ weston_compositor_build_view_list(struct weston_compositor *compositor)
 			surface_free_unused_subsurface_views(view->surface);
 }
 
+static void
+timespec_add_nsec(struct timespec *result,
+		  const struct timespec *base, int64_t ns)
+{
+	lldiv_t v;
+
+	v = lldiv(base->tv_nsec + ns, NSEC_PER_SEC);
+	if (v.rem < 0) {
+		--v.quot;
+		v.rem += NSEC_PER_SEC;
+	}
+
+	result->tv_sec = base->tv_sec + v.quot;
+	result->tv_nsec = v.rem;
+}
+
+static int64_t
+timespec_diff_to_cycles_floor(const struct timespec *a,
+			      const struct timespec *b,
+			      int64_t millihz)
+{
+	int64_t d;
+
+	d = (a->tv_sec - b->tv_sec) * NSEC_PER_SEC + a->tv_nsec - b->tv_nsec;
+	d *= millihz;
+
+	/* ns * mHz = pico-counts; round down to integer counts */
+	if (d < 0)
+		return (d - ONE_PER_PICO + 1) / ONE_PER_PICO;
+	else
+		return d / ONE_PER_PICO;
+}
+
+static void
+weston_output_predict_presentation(struct weston_output *output,
+				   const struct timespec *now,
+				   struct timespec *target_out,
+				   struct timespec *cutoff_out)
+{
+	int64_t refresh_nsec = ONE_PER_PICO / output->current_mode->refresh;
+	int64_t inc;
+
+	inc = timespec_diff_to_cycles_floor(now, &output->last_finish_time,
+					    output->current_mode->refresh);
+
+	if (inc < -1)
+		weston_log("warning in %s: inc = %" PRIi64
+			   ", did time jump backwards?\n", __func__, inc);
+
+	if (inc < 0)
+		inc = 0;
+
+	timespec_add_nsec(target_out, &output->last_finish_time,
+			  (inc + 1) * refresh_nsec);
+	timespec_add_nsec(cutoff_out, target_out, refresh_nsec / 2);
+}
+
 static int
 weston_output_repaint(struct weston_output *output)
 {
@@ -1815,11 +2108,18 @@ weston_output_repaint(struct weston_output *output)
 	struct weston_frame_callback *cb, *cnext;
 	struct wl_list frame_callback_list;
 	pixman_region32_t output_damage;
+	struct timespec now;
+	struct timespec target;
+	struct timespec cutoff;
+	int repaint_again = 0;
 	int r;
 
 	if (output->destroying)
 		return 0;
 
+	clock_gettime(ec->presentation_clock, &now);
+	weston_output_predict_presentation(output, &now, &target, &cutoff);
+
 	/* Rebuild the surface list and update surface transforms up front. */
 	weston_compositor_build_view_list(ec);
 
@@ -1829,19 +2129,32 @@ weston_output_repaint(struct weston_output *output)
 		wl_list_for_each(ev, &ec->view_list, link)
 			weston_view_move_to_plane(ev, &ec->primary_plane);
 
+	assert(!output->presentation_time);
+	output->presentation_time = weston_timestamp_create();
+
 	wl_list_init(&frame_callback_list);
 	wl_list_for_each(ev, &ec->view_list, link) {
-		/* Note: This operation is safe to do multiple times on the
+		if (ev->surface->output != output)
+			continue;
+
+		/* Note: These operations are safe to do multiple times on the
 		 * same surface.
 		 */
-		if (ev->surface->output == output) {
-			wl_list_insert_list(&frame_callback_list,
-					    &ev->surface->frame_callback_list);
-			wl_list_init(&ev->surface->frame_callback_list);
-
-			wl_list_insert_list(&output->feedback_list,
-					    &ev->surface->feedback_list);
-			wl_list_init(&ev->surface->feedback_list);
+
+		if (weston_surface_queue_process(ev->surface, &target, &cutoff))
+			repaint_again = 1;
+
+		wl_list_insert_list(&frame_callback_list,
+				    &ev->surface->frame_callback_list);
+		wl_list_init(&ev->surface->frame_callback_list);
+
+		wl_list_insert_list(&output->feedback_list,
+				    &ev->surface->feedback_list);
+		wl_list_init(&ev->surface->feedback_list);
+
+		if (!ev->surface->presentation_time) {
+			ev->surface->presentation_time =
+				weston_timestamp_ref(output->presentation_time);
 		}
 	}
 
@@ -1862,6 +2175,12 @@ weston_output_repaint(struct weston_output *output)
 
 	output->repaint_needed = 0;
 
+	/* XXX TODO even if we not repainting at all, we should still
+	 * once in a while process all queues just to discard updates. */
+
+	if (repaint_again)
+		weston_output_schedule_repaint(output);
+
 	weston_compositor_repick(ec);
 	wl_event_loop_dispatch(ec->input_loop, 0);
 
@@ -1898,7 +2217,15 @@ weston_output_finish_frame(struct weston_output *output,
 	int fd, r;
 	uint32_t refresh_nsec;
 
-	refresh_nsec = 1000000000000UL / output->current_mode->refresh;
+	output->last_finish_time = *stamp;
+
+	if (output->presentation_time) {
+		output->presentation_time->stamp = *stamp;
+		weston_timestamp_unref(output->presentation_time);
+		output->presentation_time = NULL;
+	}
+
+	refresh_nsec = ONE_PER_PICO / output->current_mode->refresh;
 	weston_presentation_feedback_present_list(&output->feedback_list,
 						  output, refresh_nsec, stamp,
 						  output->msc);
@@ -2112,14 +2439,21 @@ weston_surface_commit(struct weston_surface *surface)
 
 	/* XXX: wl_viewport.set without an attach should call configure */
 
-	/* wl_surface.set_buffer_transform */
-	/* wl_surface.set_buffer_scale */
 	/* wl_viewport.set */
-	surface->buffer_viewport = surface->pending.buffer_viewport;
+	surface->buffer_viewport.surface =
+		surface->pending.buffer_viewport.surface;
 
 	/* wl_surface.attach */
-	if (surface->pending.newly_attached)
+	if (surface->pending.newly_attached) {
+		/* wl_surface.set_buffer_transform */
+		/* wl_surface.set_buffer_scale */
+		/* wl_viewport.set */
+		surface->buffer_viewport.buffer =
+			surface->pending.buffer_viewport.buffer;
+
+		weston_surface_discard_queue(surface);
 		weston_surface_attach(surface, surface->pending.buffer);
+	}
 
 	if (surface->configure && surface->pending.newly_attached)
 		surface->configure(surface,
@@ -2192,6 +2526,11 @@ surface_commit(struct wl_client *client, struct wl_resource *resource)
 	struct weston_surface *surface = wl_resource_get_user_data(resource);
 	struct weston_subsurface *sub = weston_surface_to_subsurface(surface);
 
+	if (surface->pending.target_timestamp.tv_nsec != -1) {
+		weston_surface_queue_update(surface);
+		return;
+	}
+
 	if (sub) {
 		weston_subsurface_commit(sub);
 		return;
@@ -2341,14 +2680,33 @@ weston_subsurface_commit_from_cache(struct weston_subsurface *sub)
 	struct weston_view *view;
 	pixman_region32_t opaque;
 
-	/* wl_surface.set_buffer_transform */
-	/* wl_surface.set_buffer_scale */
 	/* wl_viewport.set */
-	surface->buffer_viewport = sub->cached.buffer_viewport;
+	surface->buffer_viewport.surface = sub->cached.buffer_viewport.surface;
 
 	/* wl_surface.attach */
-	if (sub->cached.newly_attached)
+	if (sub->cached.newly_attached) {
+		/* XXX:
+		 * Parent sets sync mode, tells subsurface to resize.
+		 * Subsurface does an immediate commit to push new size
+		 * ASAP and discard the queue, resumes queueing.
+		 * Parent takes so long, that queued updates start applying.
+		 * Without wl_viewport the window becomes inconsistent.
+		 * Parent commits, causes commit_from_cache, subsurface
+		 * content jumps backwards in time.
+		 * Solutions:
+		 * - If subsurface queues, it should not do immediate commits.
+		 * - If subsurface does immediate commit, it needs to wait
+		 * for frame callback before queueing again.
+		 */
+
+		/* wl_surface.set_buffer_transform */
+		/* wl_surface.set_buffer_scale */
+		/* wl_viewport.set */
+		surface->buffer_viewport.buffer =
+			sub->cached.buffer_viewport.buffer;
+
 		weston_surface_attach(surface, sub->cached.buffer_ref.buffer);
+	}
 	weston_buffer_reference(&sub->cached.buffer_ref, NULL);
 
 	if (surface->configure && sub->cached.newly_attached)
@@ -2423,7 +2781,15 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub)
 			      &surface->pending.damage);
 	empty_region(&surface->pending.damage);
 
+	sub->cached.buffer_viewport.surface =
+		surface->pending.buffer_viewport.surface;
+
 	if (surface->pending.newly_attached) {
+		sub->cached.buffer_viewport.buffer =
+			surface->pending.buffer_viewport.buffer;
+
+		weston_surface_discard_queue(surface);
+
 		sub->cached.newly_attached = 1;
 		weston_buffer_reference(&sub->cached.buffer_ref,
 					surface->pending.buffer);
@@ -2435,8 +2801,6 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub)
 
 	weston_surface_reset_pending_buffer(surface);
 
-	sub->cached.buffer_viewport = surface->pending.buffer_viewport;
-
 	pixman_region32_copy(&sub->cached.opaque, &surface->pending.opaque);
 
 	pixman_region32_copy(&sub->cached.input, &surface->pending.input);
@@ -3656,23 +4020,39 @@ err_calloc:
 
 static void
 presentation_queue(struct wl_client *client,
-		   struct wl_resource *resource,
-		   struct wl_resource *surface,
+		   struct wl_resource *presentation_resource,
+		   struct wl_resource *surface_resource,
 		   uint32_t tv_sec_hi,
 		   uint32_t tv_sec_lo,
 		   uint32_t tv_nsec)
 {
-	wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_METHOD,
-			       "presentation_queue unimplemented");
+	struct weston_surface *surface;
+
+	surface = wl_resource_get_user_data(surface_resource);
+
+	if (tv_nsec > 999999999) {
+		wl_resource_post_error(presentation_resource,
+			PRESENTATION_ERROR_INVALID_TIMESTAMP,
+			"presentation.queue: tv_nsec: %u out of range",
+			tv_nsec);
+		return;
+	}
+
+	surface->pending.target_timestamp.tv_sec =
+		((uint64_t)tv_sec_hi << 32) + tv_sec_lo;
+	surface->pending.target_timestamp.tv_nsec = tv_nsec;
 }
 
 static void
 presentation_discard_queue(struct wl_client *client,
-			   struct wl_resource *resource,
-			   struct wl_resource *surface)
+			   struct wl_resource *presentation_resource,
+			   struct wl_resource *surface_resource)
 {
-	wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_METHOD,
-			       "presentation_discard_queue unimplemented");
+	struct weston_surface *surface;
+
+	surface = wl_resource_get_user_data(surface_resource);
+
+	weston_surface_discard_queue(surface);
 }
 
 static const struct presentation_interface presentation_implementation = {
diff --git a/src/compositor.h b/src/compositor.h
index c94f836..2bc378c 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -173,6 +173,8 @@ enum weston_mode_switch_op {
 	WESTON_MODE_SWITCH_RESTORE_NATIVE
 };
 
+struct weston_timestamp;
+
 struct weston_output {
 	uint32_t id;
 	char *name;
@@ -199,6 +201,7 @@ struct weston_output {
 	int move_x, move_y;
 	uint32_t frame_time; /* presentation timestamp in milliseconds */
 	uint64_t msc;        /* media stream counter */
+	struct timespec last_finish_time;
 	int disable_planes;
 	int destroying;
 	struct wl_list feedback_list;
@@ -234,6 +237,9 @@ struct weston_output {
 			  uint16_t *r,
 			  uint16_t *g,
 			  uint16_t *b);
+
+	/* Used only between output_repaint and finish_frame */
+	struct weston_timestamp *presentation_time;
 };
 
 struct weston_pointer_grab;
@@ -863,6 +869,12 @@ struct weston_surface {
 	struct weston_output *output;
 
 	/*
+	 * Timestamp from presentation clock when the surface's current
+	 * contents were presented.
+	 */
+	struct weston_timestamp *presentation_time;
+
+	/*
 	 * A more complete representation of all outputs this surface is
 	 * displayed on.
 	 */
@@ -906,6 +918,9 @@ struct weston_surface {
 		/* wl_surface.set_scaling_factor */
 		/* wl_viewport.set */
 		struct weston_buffer_viewport buffer_viewport;
+
+		/* presentation.queue */
+		struct timespec target_timestamp;
 	} pending;
 
 	/*
@@ -922,6 +937,9 @@ struct weston_surface {
 	 */
 	struct wl_list subsurface_list; /* weston_subsurface::parent_link */
 	struct wl_list subsurface_list_pending; /* ...::parent_link_pending */
+
+	/* committed presentation queue, in increasing timestamp order */
+	struct wl_list queue_list; /* weston_queued_update::queue_link */
 };
 
 enum weston_key_state_update {
-- 
1.8.3.2



More information about the wayland-devel mailing list