summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPekka Paalanen <pekka.paalanen@collabora.co.uk>2014-02-05 09:29:05 (GMT)
committerFrederic Plourde <frederic.plourde@collabora.co.uk>2014-10-02 15:30:51 (GMT)
commit51f2692a0d64e0085730065fd7056a9e3e921642 (patch)
tree7b1c98f8e0fbb0b12fad584930412e5ffd2f282b
parente25a1efb53bc67ba64e873cf8dfc14a1b5316f33 (diff)
downloadweston-51f2692a0d64e0085730065fd7056a9e3e921642.tar.gz
weston-51f2692a0d64e0085730065fd7056a9e3e921642.tar.xz
clients: add presentation-shm demo
This started as a copy of simple-shm.c before it was converted to xdg_shell. This demo excercises the presentation feedback interface in five different modes: - A continuous repaint loop triggered by frame callbacks, and using immediate commits, just gathering presentation feedback and computing some time intervals for statistics. - The same as above, except with 1s sleep before actually repainting as a response to frame callback. This tests how well the compositor can do a repaint from idle state (not continuously repainting), assuming nothing else is causing repaints. - A continuous repaint loop triggered by 'presented' events rather than by frame callbacks. If Weston uses an appropriate scheduling algorithm, this mode achieves the smallest possible frame latency (below one output refresh period). - A burst mode, where it queues around 60 frames, and manages to sleep without any Wayland protocol traffic for around 1 second on a 60 Hz display while still animating continuously, until it needs to send the next burst. - A burst mode with presentation feedback for every frame. In all modes, all frames are pre-rendered at startup, so no rendering happens during the animation. By default, queueing the frames happens in random order, to test the compositor's ability to order queued updates by the timestamp. The target timestamps for queued frames are produced by a trivial algorithm, without any attempt to explicitly and quickly synchronize to the refresh cycle. Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am8
-rw-r--r--clients/presentation-shm.c999
3 files changed, 1008 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index fbffaa5..d521bcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@ weston-gears
weston-image
weston-nested
weston-nested-client
+weston-presentation-shm
weston-resizor
weston-scaler
weston-simple-egl
diff --git a/Makefile.am b/Makefile.am
index 87204a6..10be920 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -393,6 +393,7 @@ demo_clients += \
weston-simple-shm \
weston-simple-damage \
weston-simple-touch \
+ weston-presentation-shm \
weston-multi-resource
weston_simple_shm_SOURCES = clients/simple-shm.c
@@ -419,6 +420,13 @@ weston_simple_touch_SOURCES = clients/simple-touch.c
weston_simple_touch_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS)
weston_simple_touch_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la
+weston_presentation_shm_SOURCES = clients/presentation-shm.c
+nodist_weston_presentation_shm_SOURCES = \
+ protocol/presentation_timing-protocol.c \
+ protocol/presentation_timing-client-protocol.h
+weston_presentation_shm_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS)
+weston_presentation_shm_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la -lm
+
weston_multi_resource_SOURCES = clients/multi-resource.c
weston_multi_resource_CFLAGS = $(AM_CFLAGS) $(SIMPLE_CLIENT_CFLAGS)
weston_multi_resource_LDADD = $(SIMPLE_CLIENT_LIBS) libshared.la -lm
diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c
new file mode 100644
index 0000000..b04a920
--- /dev/null
+++ b/clients/presentation-shm.c
@@ -0,0 +1,999 @@
+/*
+ * Copyright © 2011 Benjamin Franzke
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2014 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <time.h>
+
+#include <wayland-client.h>
+#include "../shared/os-compatibility.h"
+#include "presentation_timing-client-protocol.h"
+
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+
+enum run_mode {
+ RUN_MODE_BURST,
+ RUN_MODE_FEEDBACK,
+ RUN_MODE_FEEDBACK_IDLE,
+ RUN_MODE_QUEUE_FEEDBACK,
+ RUN_MODE_PRESENT,
+};
+
+struct output {
+ struct wl_output *output;
+ uint32_t name;
+ struct wl_list link;
+};
+
+struct display {
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct wl_compositor *compositor;
+ struct wl_shell *shell;
+
+ struct wl_shm *shm;
+ uint32_t formats;
+
+ struct presentation *presentation;
+ clockid_t clk_id;
+
+ struct wl_list output_list; /* struct output::link */
+};
+
+struct feedback {
+ struct window *window;
+ unsigned frame_no;
+ struct presentation_feedback *feedback;
+ struct timespec commit;
+ struct timespec target;
+ uint32_t frame_stamp;
+ struct wl_list link;
+ struct timespec present;
+};
+
+struct buffer {
+ struct wl_buffer *buffer;
+ void *shm_data;
+ int busy;
+};
+
+struct window {
+ struct display *display;
+ int width, height;
+ enum run_mode mode;
+ int randomized;
+ struct wl_surface *surface;
+ struct wl_shell_surface *shell_surface;
+
+ struct buffer *buffers;
+ int num_buffers;
+ int next;
+ int refresh_nsec;
+
+ struct wl_callback *callback;
+ struct wl_list feedback_list;
+
+ struct feedback *received_feedback;
+};
+
+#define NSEC_PER_SEC 1000000000
+
+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 void
+buffer_release(void *data, struct wl_buffer *buffer)
+{
+ struct buffer *mybuf = data;
+
+ mybuf->busy = 0;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ buffer_release
+};
+
+static int
+create_shm_buffers(struct display *display, struct buffer **buffers,
+ int num_buffers, int width, int height, uint32_t format)
+{
+ struct buffer *bufs;
+ struct wl_shm_pool *pool;
+ int fd, size, stride, offset;
+ void *data;
+ int i;
+
+ stride = width * 4;
+ size = stride * height * num_buffers;
+
+ fd = os_create_anonymous_file(size);
+ if (fd < 0) {
+ fprintf(stderr, "creating a buffer file for %d B failed: %m\n",
+ size);
+ return -1;
+ }
+
+ data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ fprintf(stderr, "mmap failed: %m\n");
+ close(fd);
+ return -1;
+ }
+
+ pool = wl_shm_create_pool(display->shm, fd, size);
+ offset = 0;
+
+ bufs = calloc(num_buffers, sizeof(*bufs));
+ assert(bufs);
+
+ for (i = 0; i < num_buffers; i++) {
+ bufs[i].buffer = wl_shm_pool_create_buffer(pool, offset,
+ width, height,
+ stride, format);
+ assert(bufs[i].buffer);
+ wl_buffer_add_listener(bufs[i].buffer,
+ &buffer_listener, &bufs[i]);
+
+ bufs[i].shm_data = (char *)data + offset;
+ offset += stride * height;
+ }
+
+ wl_shm_pool_destroy(pool);
+ close(fd);
+
+ *buffers = bufs;
+
+ return 0;
+}
+
+static void
+handle_ping(void *data, struct wl_shell_surface *shell_surface,
+ uint32_t serial)
+{
+ wl_shell_surface_pong(shell_surface, serial);
+}
+
+static void
+handle_configure(void *data, struct wl_shell_surface *shell_surface,
+ uint32_t edges, int32_t width, int32_t height)
+{
+}
+
+static void
+handle_popup_done(void *data, struct wl_shell_surface *shell_surface)
+{
+}
+
+static const struct wl_shell_surface_listener shell_surface_listener = {
+ handle_ping,
+ handle_configure,
+ handle_popup_done
+};
+
+static struct window *
+create_window(struct display *display, int width, int height,
+ enum run_mode mode, int randomized)
+{
+ struct window *window;
+ int ret;
+
+ window = calloc(1, sizeof *window);
+ if (!window)
+ return NULL;
+
+ window->mode = mode;
+ window->randomized = randomized;
+ window->callback = NULL;
+ wl_list_init(&window->feedback_list);
+ window->display = display;
+ window->width = width;
+ window->height = height;
+ window->surface = wl_compositor_create_surface(display->compositor);
+ window->shell_surface = wl_shell_get_shell_surface(display->shell,
+ window->surface);
+
+ if (window->shell_surface)
+ wl_shell_surface_add_listener(window->shell_surface,
+ &shell_surface_listener, window);
+
+ wl_shell_surface_set_title(window->shell_surface, "presentation-shm");
+
+ wl_shell_surface_set_toplevel(window->shell_surface);
+
+ window->num_buffers = 60;
+ window->refresh_nsec = NSEC_PER_SEC / 60; /* 60 Hz guess */
+ window->next = 0;
+ ret = create_shm_buffers(window->display,
+ &window->buffers, window->num_buffers,
+ window->width, window->height,
+ WL_SHM_FORMAT_XRGB8888);
+ assert(ret == 0);
+
+ return window;
+}
+
+static void
+destroy_feedback(struct feedback *feedback)
+{
+ if (feedback->feedback)
+ presentation_feedback_destroy(feedback->feedback);
+
+ wl_list_remove(&feedback->link);
+ free(feedback);
+}
+
+static void
+destroy_window(struct window *window)
+{
+ int i;
+
+ while (!wl_list_empty(&window->feedback_list)) {
+ struct feedback *f;
+
+ f = wl_container_of(window->feedback_list.next, f, link);
+ printf("clean up feedback %u\n", f->frame_no);
+ destroy_feedback(f);
+ }
+
+ if (window->callback)
+ wl_callback_destroy(window->callback);
+
+ wl_shell_surface_destroy(window->shell_surface);
+ wl_surface_destroy(window->surface);
+
+ for (i = 0; i < window->num_buffers; i++)
+ wl_buffer_destroy(window->buffers[i].buffer);
+ /* munmap(window->buffers[0].shm_data, size); */
+ free(window->buffers);
+
+ free(window);
+}
+
+static struct buffer *
+window_next_buffer(struct window *window)
+{
+ struct buffer *buf = &window->buffers[window->next];
+
+ window->next = (window->next + 1) % window->num_buffers;
+
+ return buf;
+}
+
+static void
+paint_pixels(void *image, int width, int height, uint32_t phase)
+{
+ const int halfh = height / 2;
+ const int halfw = width / 2;
+ uint32_t *pixel = image;
+ int y, or;
+ double ang = M_PI * 2.0 / 1000000.0 * phase;
+ double s = sin(ang);
+ double c = cos(ang);
+
+ /* squared radii thresholds */
+ or = (halfw < halfh ? halfw : halfh) - 16;
+ or *= or;
+
+ for (y = 0; y < height; y++) {
+ int x;
+ int oy = y - halfh;
+ int y2 = oy * oy;
+
+ for (x = 0; x < width; x++) {
+ int ox = x - halfw;
+ uint32_t v = 0xff000000;
+ double rx, ry;
+
+ if (ox * ox + y2 > or) {
+ if (ox * oy > 0)
+ *pixel++ = 0xff000000;
+ else
+ *pixel++ = 0xffffffff;
+ continue;
+ }
+
+ rx = c * ox + s * oy;
+ ry = -s * ox + c * oy;
+
+ if (rx < 0.0)
+ v |= 0x00ff0000;
+ if (ry < 0.0)
+ v |= 0x0000ff00;
+ if ((rx < 0.0) == (ry < 0.0))
+ v |= 0x000000ff;
+
+ *pixel++ = v;
+ }
+ }
+}
+
+static void
+feedback_sync_output(void *data,
+ struct presentation_feedback *presentation_feedback,
+ struct wl_output *output)
+{
+ /* not interested */
+}
+
+static char *
+pflags_to_str(uint32_t flags, char *str, unsigned len)
+{
+ static const struct {
+ uint32_t flag;
+ char sym;
+ } desc[] = {
+ { PRESENTATION_FEEDBACK_KIND_VSYNC, 's' },
+ { PRESENTATION_FEEDBACK_KIND_USER_CLOCK, 'u' },
+ { PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'h' },
+ { PRESENTATION_FEEDBACK_KIND_COPY, 'c' },
+ };
+ unsigned i;
+
+ *str = '\0';
+ if (len < 5)
+ return str;
+
+ for (i = 0; i < ARRAY_LENGTH(desc); i++)
+ str[i] = flags & desc[i].flag ? desc[i].sym : '_';
+ str[4] = '\0';
+
+ return str;
+}
+
+static uint32_t
+timespec_to_ms(const struct timespec *ts)
+{
+ return (uint32_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000;
+}
+
+static void
+timespec_from_proto(struct timespec *tm, uint32_t tv_sec_hi,
+ uint32_t tv_sec_lo, uint32_t tv_nsec)
+{
+ tm->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo;
+ tm->tv_nsec = tv_nsec;
+}
+
+static int
+timespec_diff_to_usec(const struct timespec *a, const struct timespec *b)
+{
+ time_t secs = a->tv_sec - b->tv_sec;
+ long nsec = a->tv_nsec - b->tv_nsec;
+
+ return secs * 1000000 + nsec / 1000;
+}
+
+static void
+feedback_presented(void *data,
+ struct presentation_feedback *presentation_feedback,
+ uint32_t tv_sec_hi,
+ uint32_t tv_sec_lo,
+ uint32_t tv_nsec,
+ uint32_t refresh_nsec,
+ uint32_t seq_hi,
+ uint32_t seq_lo,
+ uint32_t flags)
+{
+ struct feedback *feedback = data;
+ struct window *window = feedback->window;
+ struct feedback *prev_feedback = window->received_feedback;
+ uint64_t seq = ((uint64_t)seq_hi << 32) + seq_lo;
+ const struct timespec *prevpresent;
+ uint32_t commit, present;
+ uint32_t f2c, c2p, f2p;
+ int p2p, t2p;
+ char flagstr[10];
+
+ timespec_from_proto(&feedback->present, tv_sec_hi, tv_sec_lo, tv_nsec);
+ commit = timespec_to_ms(&feedback->commit);
+ present = timespec_to_ms(&feedback->present);
+
+ if (prev_feedback)
+ prevpresent = &prev_feedback->present;
+ else
+ prevpresent = &feedback->present;
+
+ f2c = commit - feedback->frame_stamp;
+ c2p = present - commit;
+ f2p = present - feedback->frame_stamp;
+ p2p = timespec_diff_to_usec(&feedback->present, prevpresent);
+ t2p = timespec_diff_to_usec(&feedback->present, &feedback->target);
+
+ switch (window->mode) {
+ case RUN_MODE_QUEUE_FEEDBACK:
+ case RUN_MODE_PRESENT:
+ printf("%6u: c2p %4u ms, p2p %5d us, t2p %6d us, [%s] "
+ "seq %" PRIu64 "\n", feedback->frame_no, c2p,
+ p2p, t2p,
+ pflags_to_str(flags, flagstr, sizeof(flagstr)), seq);
+ break;
+ case RUN_MODE_BURST:
+ case RUN_MODE_FEEDBACK:
+ case RUN_MODE_FEEDBACK_IDLE:
+ printf("%6u: f2c %2u ms, c2p %2u ms, f2p %2u ms, p2p %5d us, "
+ "t2p %6d, [%s], seq %" PRIu64 "\n", feedback->frame_no,
+ f2c, c2p, f2p, p2p, t2p,
+ pflags_to_str(flags, flagstr, sizeof(flagstr)), seq);
+ }
+
+ if (window->received_feedback)
+ destroy_feedback(window->received_feedback);
+ window->received_feedback = feedback;
+}
+
+static void
+feedback_discarded(void *data,
+ struct presentation_feedback *presentation_feedback)
+{
+ struct feedback *feedback = data;
+
+ printf("discarded %u\n", feedback->frame_no);
+
+ destroy_feedback(feedback);
+}
+
+static const struct presentation_feedback_listener feedback_listener = {
+ feedback_sync_output,
+ feedback_presented,
+ feedback_discarded
+};
+
+static void
+window_create_feedback(struct window *window, uint32_t frame_stamp,
+ struct timespec *target)
+{
+ static unsigned seq;
+ struct presentation *pres = window->display->presentation;
+ struct feedback *feedback;
+
+ seq++;
+
+ if (!pres)
+ return;
+
+ feedback = calloc(1, sizeof *feedback);
+ if (!feedback)
+ return;
+
+ feedback->window = window;
+ feedback->feedback = presentation_feedback(pres, window->surface);
+ presentation_feedback_add_listener(feedback->feedback,
+ &feedback_listener, feedback);
+
+ feedback->frame_no = seq;
+ clock_gettime(window->display->clk_id, &feedback->commit);
+ feedback->frame_stamp = frame_stamp;
+
+ if (target)
+ feedback->target = *target;
+ else
+ feedback->target = feedback->commit;
+
+ wl_list_insert(&window->feedback_list, &feedback->link);
+}
+
+static void
+window_commit_next(struct window *window)
+{
+ struct buffer *buffer;
+
+ buffer = window_next_buffer(window);
+ assert(buffer);
+
+ wl_surface_attach(window->surface, buffer->buffer, 0, 0);
+ wl_surface_damage(window->surface, 0, 0, window->width, window->height);
+ wl_surface_commit(window->surface);
+ buffer->busy = 1;
+}
+
+static const struct wl_callback_listener frame_listener_mode_feedback;
+
+static void
+redraw_mode_feedback(void *data, struct wl_callback *callback, uint32_t time)
+{
+ struct window *window = data;
+
+ if (callback && window->mode == RUN_MODE_FEEDBACK_IDLE)
+ sleep(1);
+
+ if (callback)
+ wl_callback_destroy(callback);
+
+ window->callback = wl_surface_frame(window->surface);
+ wl_callback_add_listener(window->callback,
+ &frame_listener_mode_feedback, window);
+
+ window_create_feedback(window, time, NULL);
+ window_commit_next(window);
+}
+
+static const struct wl_callback_listener frame_listener_mode_feedback = {
+ redraw_mode_feedback
+};
+
+static void
+presentation_queue_timespec(struct presentation *pres, struct wl_surface *s,
+ const struct timespec *ts)
+{
+ uint32_t flags = 0;
+ uint64_t secs = ts->tv_sec;
+
+ presentation_queue(pres, s, secs >> 32, secs & 0xffffffff, ts->tv_nsec,
+ flags);
+}
+
+struct index_element {
+ int index;
+ int key;
+};
+
+static int
+index_element_cmp(const void *a, const void * b)
+{
+ const struct index_element *ia = a;
+ const struct index_element *ib = b;
+
+ return ia->key - ib->key;
+}
+
+static void
+randomize_sequence(struct index_element *arr, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ arr[i].index = i;
+ arr[i].key = random();
+ }
+
+ qsort(arr, n, sizeof *arr, index_element_cmp);
+}
+
+static const struct presentation_feedback_listener feedkick_listener;
+
+static void
+window_feedkick(struct window *window)
+{
+ struct presentation *pres = window->display->presentation;
+ struct presentation_feedback *fback;
+
+ fback = presentation_feedback(pres, window->surface);
+ presentation_feedback_add_listener(fback, &feedkick_listener, window);
+}
+
+static void
+queue_more_frames(struct window *window, const struct timespec *base)
+{
+ struct presentation *pres = window->display->presentation;
+ const int n = window->num_buffers;
+ int begin = window->next;
+ int count = 0;
+ int i;
+ struct index_element ie[100];
+
+ assert(ARRAY_LENGTH(ie) >= (unsigned)n);
+
+ while (count < n && !window->buffers[(count + begin) % n].busy)
+ count++;
+
+ if (count < 1) {
+ fprintf(stderr, "Error: All busy?!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ randomize_sequence(ie, count);
+ for (i = 0; i < count; i++) {
+ int ind;
+ struct buffer *buffer;
+ struct timespec target;
+
+ if (window->randomized)
+ ind = ie[i].index;
+ else
+ ind = i;
+
+ buffer = &window->buffers[(begin + ind) % n];
+
+ timespec_add_nsec(&target, base,
+ (ind + 2) * window->refresh_nsec);
+ presentation_queue_timespec(pres, window->surface, &target);
+
+ if (ind == count - 2)
+ window_feedkick(window);
+
+ if (window->mode == RUN_MODE_QUEUE_FEEDBACK)
+ window_create_feedback(window, 0, &target);
+
+ wl_surface_attach(window->surface, buffer->buffer, 0, 0);
+ wl_surface_commit(window->surface);
+ buffer->busy = 1;
+ }
+
+ window->next = (begin + count) % n;
+}
+
+static void
+feedkick_presented(void *data,
+ struct presentation_feedback *presentation_feedback,
+ uint32_t tv_sec_hi,
+ uint32_t tv_sec_lo,
+ uint32_t tv_nsec,
+ uint32_t refresh_nsec,
+ uint32_t seq_hi,
+ uint32_t seq_lo,
+ uint32_t flags)
+{
+ struct window *window = data;
+ struct timespec base;
+
+ presentation_feedback_destroy(presentation_feedback);
+ timespec_from_proto(&base, tv_sec_hi, tv_sec_lo, tv_nsec);
+ window->refresh_nsec = refresh_nsec;
+
+ switch (window->mode) {
+ case RUN_MODE_QUEUE_FEEDBACK:
+ case RUN_MODE_BURST:
+ queue_more_frames(window, &base);
+ break;
+ case RUN_MODE_PRESENT:
+ window_create_feedback(window, 0, NULL);
+ window_feedkick(window);
+ window_commit_next(window);
+ break;
+ case RUN_MODE_FEEDBACK:
+ case RUN_MODE_FEEDBACK_IDLE:
+ assert(0 && "bad mode");
+ }
+}
+
+static void
+feedkick_discarded(void *data,
+ struct presentation_feedback *presentation_feedback)
+{
+ struct window *window = data;
+ struct timespec base;
+
+ presentation_feedback_destroy(presentation_feedback);
+ clock_gettime(window->display->clk_id, &base);
+ queue_more_frames(window, &base);
+}
+
+static const struct presentation_feedback_listener feedkick_listener = {
+ feedback_sync_output,
+ feedkick_presented,
+ feedkick_discarded
+};
+
+static void
+firstdraw_mode_burst(struct window *window)
+{
+ switch (window->mode) {
+ case RUN_MODE_PRESENT:
+ case RUN_MODE_QUEUE_FEEDBACK:
+ window_create_feedback(window, 0, NULL);
+ break;
+ case RUN_MODE_BURST:
+ break;
+ case RUN_MODE_FEEDBACK:
+ case RUN_MODE_FEEDBACK_IDLE:
+ assert(0 && "bad mode");
+ }
+
+ window_feedkick(window);
+ window_commit_next(window);
+}
+
+static void
+window_prerender(struct window *window)
+{
+ int i;
+ int timefactor = 1000000 / window->num_buffers;
+
+ for (i = 0; i < window->num_buffers; i++) {
+ struct buffer *buf = &window->buffers[i];
+
+ if (buf->busy)
+ fprintf(stderr, "wl_buffer id %u) busy\n",
+ wl_proxy_get_id(
+ (struct wl_proxy *)buf->buffer));
+
+ paint_pixels(buf->shm_data, window->width, window->height,
+ i * timefactor);
+ }
+}
+
+static void
+output_destroy(struct output *o)
+{
+ wl_output_destroy(o->output);
+ wl_list_remove(&o->link);
+ free(o);
+}
+
+static void
+display_add_output(struct display *d, uint32_t name, uint32_t version)
+{
+ struct output *o;
+
+ o = calloc(1, sizeof(*o));
+ assert(o);
+
+ o->output = wl_registry_bind(d->registry, name,
+ &wl_output_interface, 1);
+ o->name = name;
+ wl_list_insert(&d->output_list, &o->link);
+}
+
+static void
+presentation_clock_id(void *data, struct presentation *presentation,
+ uint32_t clk_id)
+{
+ struct display *d = data;
+
+ d->clk_id = clk_id;
+}
+
+static const struct presentation_listener presentation_listener = {
+ presentation_clock_id
+};
+
+static void
+shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
+{
+ struct display *d = data;
+
+ d->formats |= (1 << format);
+}
+
+static const struct wl_shm_listener shm_listener = {
+ shm_format
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version)
+{
+ struct display *d = data;
+
+ if (strcmp(interface, "wl_compositor") == 0) {
+ d->compositor =
+ wl_registry_bind(registry,
+ name, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, "wl_shell") == 0) {
+ d->shell = wl_registry_bind(registry,
+ name, &wl_shell_interface, 1);
+ } else if (strcmp(interface, "wl_shm") == 0) {
+ d->shm = wl_registry_bind(registry,
+ name, &wl_shm_interface, 1);
+ wl_shm_add_listener(d->shm, &shm_listener, d);
+ } else if (strcmp(interface, "wl_output") == 0) {
+ display_add_output(d, name, version);
+ } else if (strcmp(interface, "presentation") == 0) {
+ d->presentation =
+ wl_registry_bind(registry,
+ name, &presentation_interface, 1);
+ presentation_add_listener(d->presentation,
+ &presentation_listener, d);
+ }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+ uint32_t name)
+{
+ struct display *d = data;
+ struct output *output, *otmp;
+
+ wl_list_for_each_safe(output, otmp, &d->output_list, link) {
+ if (output->name != name)
+ continue;
+
+ output_destroy(output);
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+static struct display *
+create_display(void)
+{
+ struct display *display;
+
+ display = malloc(sizeof *display);
+ if (display == NULL) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ display->display = wl_display_connect(NULL);
+ assert(display->display);
+
+ display->formats = 0;
+ display->clk_id = -1;
+ wl_list_init(&display->output_list);
+ display->registry = wl_display_get_registry(display->display);
+ wl_registry_add_listener(display->registry,
+ &registry_listener, display);
+ wl_display_roundtrip(display->display);
+ if (display->shm == NULL) {
+ fprintf(stderr, "No wl_shm global\n");
+ exit(1);
+ }
+
+ wl_display_roundtrip(display->display);
+
+ if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) {
+ fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n");
+ exit(1);
+ }
+
+ wl_display_get_fd(display->display);
+
+ return display;
+}
+
+static void
+destroy_display(struct display *display)
+{
+ while (!wl_list_empty(&display->output_list)) {
+ struct output *o;
+
+ o = wl_container_of(display->output_list.next, o, link);
+ output_destroy(o);
+ }
+
+ if (display->shm)
+ wl_shm_destroy(display->shm);
+
+ if (display->shell)
+ wl_shell_destroy(display->shell);
+
+ if (display->compositor)
+ wl_compositor_destroy(display->compositor);
+
+ wl_registry_destroy(display->registry);
+ wl_display_flush(display->display);
+ wl_display_disconnect(display->display);
+ free(display);
+}
+
+static int running = 1;
+
+static void
+signal_int(int signum)
+{
+ running = 0;
+}
+
+static void
+usage(const char *prog, int exit_code)
+{
+ fprintf(stderr, "Usage: %s [mode] [options]\n"
+ "where 'mode' is one of\n"
+ " -b\trun in burst mode\n"
+ " -f\trun in feedback mode (default)\n"
+ " -i\trun in feedback-idle mode; sleep 1s between frames\n"
+ " -p\trun in low-latency presentation mode\n"
+ " -q\trun in queue (burst) mode with feedback\n"
+ "and 'options' may include\n"
+ " -n\tdo not randomize the queueing order\n\n",
+ prog);
+
+ fprintf(stderr, "Printed timing statistics, depending on mode:\n"
+ " commit sequence number\n"
+ " f2c: time from frame callback timestamp to commit\n"
+ " c2p: time from commit to presentation\n"
+ " f2p: time from frame callback timestamp to presentation\n"
+ " p2p: time from previous presentation to this one\n"
+ " t2p: time from target timestamp to presentation\n"
+ " seq: MSC\n");
+
+
+ exit(exit_code);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct sigaction sigint;
+ struct display *display;
+ struct window *window;
+ int ret = 0;
+ enum run_mode mode = RUN_MODE_FEEDBACK;
+ int randomized = 1;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp("-b", argv[i]) == 0)
+ mode = RUN_MODE_BURST;
+ else if (strcmp("-f", argv[i]) == 0)
+ mode = RUN_MODE_FEEDBACK;
+ else if (strcmp("-i", argv[i]) == 0)
+ mode = RUN_MODE_FEEDBACK_IDLE;
+ else if (strcmp("-p", argv[i]) == 0)
+ mode = RUN_MODE_PRESENT;
+ else if (strcmp("-q", argv[i]) == 0)
+ mode = RUN_MODE_QUEUE_FEEDBACK;
+ else if (strcmp("-n", argv[i]) == 0)
+ randomized = 0;
+ else
+ usage(argv[0], EXIT_FAILURE);
+ }
+
+ display = create_display();
+ window = create_window(display, 250, 250, mode, randomized);
+ if (!window)
+ return 1;
+
+ sigint.sa_handler = signal_int;
+ sigemptyset(&sigint.sa_mask);
+ sigint.sa_flags = SA_RESETHAND;
+ sigaction(SIGINT, &sigint, NULL);
+
+ window_prerender(window);
+
+ switch (mode) {
+ case RUN_MODE_FEEDBACK:
+ case RUN_MODE_FEEDBACK_IDLE:
+ redraw_mode_feedback(window, NULL, 0);
+ break;
+ case RUN_MODE_QUEUE_FEEDBACK:
+ case RUN_MODE_BURST:
+ case RUN_MODE_PRESENT:
+ firstdraw_mode_burst(window);
+ break;
+ }
+
+ while (running && ret != -1)
+ ret = wl_display_dispatch(display->display);
+
+ fprintf(stderr, "presentation-shm exiting\n");
+ destroy_window(window);
+ destroy_display(display);
+
+ return 0;
+}