diff --git a/tests/argb8888_implicit.dump b/tests/argb8888_implicit.dump
new file mode 100644
index 0000000..9c5fffd
Binary files /dev/null and b/tests/argb8888_implicit.dump differ
diff --git a/tests/argb8888_linear.dump b/tests/argb8888_linear.dump
new file mode 100644
index 0000000..9c5fffd
Binary files /dev/null and b/tests/argb8888_linear.dump differ
diff --git a/tests/dmabuf_test.c b/tests/dmabuf_test.c
new file mode 100644
index 0000000..7d0d902
--- /dev/null
+++ b/tests/dmabuf_test.c
@@ -0,0 +1,735 @@
+/* Tests for the Wayland compositor running on the X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "test_harness.h"
+#include "linux-dmabuf-unstable-v1.h"
+
+/* N.B. that this test will have to be adjusted once multiple devices
+ are fully supported. */
+
+enum test_kind
+ {
+ ARGB8888_KIND,
+ ARGB8888_LINEAR_KIND,
+ };
+
+static const char *test_names[] =
+ {
+ "argb8888",
+ "argb8888_linear",
+ };
+
+#define LAST_TEST ARGB8888_LINEAR_KIND
+
+struct test_params_data
+{
+ /* The buffer. */
+ struct wl_buffer *buffer;
+
+ /* Flag that indicates completion. */
+ bool complete;
+};
+
+struct test_feedback_tranche
+{
+ /* The next tranche (with higher priority). */
+ struct test_feedback_tranche *next;
+
+ /* Array of indices into the format-modifier table. */
+ uint16_t *indices;
+
+ /* Number of format-modifier pairs supported. */
+ int n_indices;
+};
+
+struct test_feedback_data
+{
+ /* The device node of the main device. */
+ dev_t device;
+
+ /* The file descriptor of the format-modifier table. */
+ int fd;
+
+ /* The size of the format-modifier table. */
+ uint32_t format_table_size;
+
+ /* List of tranches. */
+ struct test_feedback_tranche *tranches;
+
+ /* Whether or not a tranche is being recorded. */
+ bool recording_tranche;
+};
+
+struct format_modifier_pair
+{
+ /* See the documentation of
+ zwp_linux_dmabuf_feedback_v1::format_table for more details. */
+ uint32_t format;
+ uint32_t padding;
+ uint64_t modifier;
+};
+
+/* The display. */
+static struct test_display *display;
+
+/* The dmabuf interface. */
+static struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1;
+
+/* Test interfaces. */
+static struct test_interface test_interfaces[] =
+ {
+ { "zwp_linux_dmabuf_v1", &linux_dmabuf_v1,
+ &zwp_linux_dmabuf_v1_interface, 4, },
+ };
+
+/* The test surface window. */
+static Window test_surface_window;
+
+/* The test surface and Wayland surface. */
+static struct test_surface *test_surface;
+static struct wl_surface *wayland_surface;
+
+/* The GBM device. */
+static struct gbm_device *gbm_device;
+
+/* The format-modifier table. */
+static struct format_modifier_pair *modifier_table;
+
+/* List of tranches. */
+static struct test_feedback_tranche *feedback_tranches;
+
+
+
+/* Forward declarations. */
+static void submit_frame_callback (struct wl_surface *, enum test_kind);
+static void submit_surface_damage (struct wl_surface *, int, int, int, int);
+static struct wl_buffer *create_rainbow_buffer (uint32_t, uint64_t,
+ uint32_t, uint32_t,
+ uint32_t);
+static bool is_format_supported (uint32_t, uint64_t);
+
+
+
+static void
+verify_single_step (enum test_kind kind)
+{
+ switch (kind)
+ {
+ case ARGB8888_KIND:
+ verify_image_data (display, test_surface_window,
+ "argb8888_implicit.dump");
+ break;
+
+ case ARGB8888_LINEAR_KIND:
+ verify_image_data (display, test_surface_window,
+ "argb8888_linear.dump");
+ break;
+
+ default:
+ break;
+ }
+
+ if (kind == LAST_TEST)
+ test_complete ();
+}
+
+static void
+test_single_step (enum test_kind kind)
+{
+ struct wl_buffer *buffer;
+
+ test_log ("running test step: %s", test_names[kind]);
+
+ switch (kind)
+ {
+ case ARGB8888_KIND:
+ buffer = create_rainbow_buffer (GBM_FORMAT_ARGB8888,
+ DRM_FORMAT_MOD_INVALID,
+ 0xffff0000,
+ 0xff00ff00,
+ 0xff0000ff);
+
+ if (!buffer)
+ report_test_failure ("failed to create ARGB8888 buffer");
+
+ wl_surface_attach (wayland_surface, buffer, 0, 0);
+ submit_surface_damage (wayland_surface, 0, 0,
+ INT_MAX, INT_MAX);
+ submit_frame_callback (wayland_surface, kind);
+ wl_surface_commit (wayland_surface);
+ wl_buffer_destroy (buffer);
+ break;
+
+ case ARGB8888_LINEAR_KIND:
+
+ if (!is_format_supported (DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_MOD_LINEAR))
+ {
+ test_log ("skipping ARGB888 with linear modifier as"
+ " that is not supported");
+ test_complete ();
+ }
+
+ buffer = create_rainbow_buffer (GBM_FORMAT_ARGB8888,
+ DRM_FORMAT_MOD_LINEAR,
+ 0xffff0000,
+ 0xff00ff00,
+ 0xff0000ff);
+
+ if (!buffer)
+ report_test_failure ("failed to create ARGB8888 buffer"
+ " with linear storage format");
+
+ wl_surface_attach (wayland_surface, buffer, 0, 0);
+ submit_surface_damage (wayland_surface, 0, 0,
+ INT_MAX, INT_MAX);
+ submit_frame_callback (wayland_surface, kind);
+ wl_surface_commit (wayland_surface);
+ wl_buffer_destroy (buffer);
+ break;
+ }
+}
+
+static void
+test_next_step (enum test_kind kind)
+{
+ switch (kind)
+ {
+ case ARGB8888_KIND:
+ test_single_step (ARGB8888_LINEAR_KIND);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+
+static void
+handle_test_surface_mapped (void *data, struct test_surface *surface,
+ uint32_t xid, const char *display_string)
+{
+ /* Sleep for 1 second to ensure that the window is exposed and
+ redirected. */
+ sleep (1);
+
+ /* Start the test. */
+ test_surface_window = xid;
+}
+
+static const struct test_surface_listener test_surface_listener =
+ {
+ handle_test_surface_mapped,
+ };
+
+
+
+static void
+handle_wl_callback_done (void *data, struct wl_callback *callback,
+ uint32_t callback_data)
+{
+ enum test_kind kind;
+
+ /* kind is not a pointer. It is an enum test_kind stuffed into a
+ pointer. */
+ kind = (intptr_t) data;
+
+ wl_callback_destroy (callback);
+ verify_single_step (kind);
+
+ /* Now run the next test in this sequence. */
+ test_next_step (kind);
+}
+
+static const struct wl_callback_listener wl_callback_listener =
+ {
+ handle_wl_callback_done,
+ };
+
+
+
+static void
+submit_frame_callback (struct wl_surface *surface, enum test_kind kind)
+{
+ struct wl_callback *callback;
+
+ callback = wl_surface_frame (surface);
+ wl_callback_add_listener (callback, &wl_callback_listener,
+ (void *) (intptr_t) kind);
+}
+
+static void
+submit_surface_damage (struct wl_surface *surface, int x, int y, int width,
+ int height)
+{
+ test_log ("damaging surface by %d, %d, %d, %d", x, y, width,
+ height);
+
+ wl_surface_damage (surface, x, y, width, height);
+}
+
+
+
+static void
+handle_feedback_done (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback)
+{
+ struct test_feedback_data *test_data;
+
+ test_data = data;
+
+ if (test_data->recording_tranche)
+ report_test_failure ("done received while recording tranche");
+}
+
+static void
+handle_feedback_format_table (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback,
+ int32_t fd, uint32_t size)
+{
+ struct test_feedback_data *test_data;
+
+ test_data = data;
+
+ test_data->fd = fd;
+ test_data->format_table_size = size;
+}
+
+static void
+handle_feedback_main_device (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback,
+ struct wl_array *device)
+{
+ struct test_feedback_data *test_data;
+
+ test_data = data;
+
+ if (device->size != sizeof test_data->device)
+ report_test_failure ("got incorrect array size for dev_t");
+
+ memcpy (&test_data->device, device->data, device->size);
+}
+
+static void
+handle_feedback_tranche_done (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback)
+{
+ struct test_feedback_data *test_data;
+
+ test_data = data;
+
+ if (!test_data->recording_tranche)
+ report_test_failure ("tranche_done received but not recording tranche");
+
+ test_data->recording_tranche = false;
+}
+
+static void
+handle_feedback_tranche_target_device (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback,
+ struct wl_array *device)
+{
+ /* Nothing to do here. */
+}
+
+static void
+handle_feedback_tranche_formats (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback,
+ struct wl_array *indices)
+{
+ struct test_feedback_data *test_data;
+ struct test_feedback_tranche *tranche;
+
+ test_data = data;
+
+ if (!test_data->recording_tranche)
+ {
+ /* Start recording a new tranche. */
+ tranche = calloc (1, sizeof *tranche);
+
+ if (!tranche)
+ report_test_failure ("failed to allocate tranche");
+
+ tranche->next = test_data->tranches;
+ test_data->tranches = tranche;
+ test_data->recording_tranche = true;
+ }
+ else
+ tranche = test_data->tranches;
+
+ if (tranche->indices)
+ free (tranche->indices);
+
+ if (indices->size % sizeof (uint16_t))
+ report_test_failure ("invalid tranche size: %zu", indices->size);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
+#endif
+
+ tranche->indices = malloc (indices->size);
+ tranche->n_indices = indices->size / sizeof (uint16_t);
+
+ if (!tranche->indices)
+ report_test_failure ("failed to allocate tranche indices");
+
+ memcpy (tranche->indices, indices->data, indices->size);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
+
+static void
+handle_feedback_tranche_flags (void *data,
+ struct zwp_linux_dmabuf_feedback_v1 *feedback,
+ uint32_t flags)
+{
+ /* Nothing to do here. */
+}
+
+static struct zwp_linux_dmabuf_feedback_v1_listener feedback_listener =
+ {
+ handle_feedback_done,
+ handle_feedback_format_table,
+ handle_feedback_main_device,
+ handle_feedback_tranche_done,
+ handle_feedback_tranche_target_device,
+ handle_feedback_tranche_formats,
+ handle_feedback_tranche_flags,
+ };
+
+
+
+static int
+open_device (dev_t device)
+{
+ drmDevicePtr device_ptr;
+ int fd;
+
+ if (drmGetDeviceFromDevId (device, 0, &device_ptr) < 0)
+ return -1;
+
+ fd = -1;
+
+ if (device_ptr->available_nodes & (1 << DRM_NODE_RENDER))
+ /* Open the render node if available. */
+ fd = open (device_ptr->nodes[DRM_NODE_RENDER], O_RDWR);
+
+ /* Free the device. */
+ drmFreeDevice (&device_ptr);
+
+ /* Return the file descriptor. */
+ return fd;
+}
+
+static void
+open_surface (void)
+{
+ struct zwp_linux_dmabuf_feedback_v1 *feedback;
+ struct test_feedback_data data;
+ struct test_feedback_tranche *tranche;
+ int fd, i;
+
+ feedback
+ = zwp_linux_dmabuf_v1_get_default_feedback (linux_dmabuf_v1);
+
+ if (!feedback)
+ report_test_failure ("failed to create dmabuf feedback");
+
+ memset (&data, 0, sizeof data);
+ data.fd = -1;
+
+ zwp_linux_dmabuf_feedback_v1_add_listener (feedback, &feedback_listener,
+ &data);
+ wl_display_roundtrip (display->display);
+
+ /* Now verify that everything required arrived. */
+ if (!data.device || data.fd < 0
+ || (data.format_table_size
+ % sizeof (struct format_modifier_pair))
+ || !data.tranches)
+ report_test_failure ("received invalid parameters from feedback");
+
+ /* Open the provided node. */
+ fd = open_device (data.device);
+
+ if (fd < 0)
+ report_test_failure ("failed to open device");
+
+ gbm_device = gbm_create_device (fd);
+
+ if (!gbm_device)
+ report_test_failure ("failed to open device");
+
+ /* Now try to map the format-modifier table and verify the validity
+ of each tranche. */
+ modifier_table = mmap (NULL, data.format_table_size, PROT_READ,
+ MAP_PRIVATE, data.fd, 0);
+
+ if (modifier_table == (void *) -1)
+ report_test_failure ("failed to map modifier table");
+
+ for (tranche = data.tranches; tranche; tranche = tranche->next)
+ {
+ for (i = 0; i < tranche->n_indices; ++i)
+ {
+ if (tranche->indices[i]
+ >= data.format_table_size / sizeof *modifier_table)
+ report_test_failure ("tranche index %"PRIu16" extends outside"
+ " bounds of format modifier table",
+ tranche->indices[i]);
+ }
+ }
+
+ zwp_linux_dmabuf_feedback_v1_destroy (feedback);
+ feedback_tranches = data.tranches;
+}
+
+static bool
+is_format_supported (uint32_t format, uint64_t modifier)
+{
+ struct test_feedback_tranche *tranche;
+ int i;
+ struct format_modifier_pair pair;
+
+ /* Loop through each tranche, looking for a matching entry in the
+ targets table. */
+ for (tranche = feedback_tranches; tranche; tranche = tranche->next)
+ {
+ for (i = 0; i < tranche->n_indices; ++i)
+ {
+ pair = modifier_table[i];
+
+ if (pair.format == format
+ && pair.modifier == modifier)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+static void
+handle_params_created (void *data,
+ struct zwp_linux_buffer_params_v1 *params,
+ struct wl_buffer *buffer)
+{
+ struct test_params_data *params_data;
+
+ params_data = data;
+ params_data->complete = true;
+ params_data->buffer = buffer;
+}
+
+static void
+handle_params_failed (void *data,
+ struct zwp_linux_buffer_params_v1 *params)
+{
+ struct test_params_data *params_data;
+
+ params_data = data;
+
+ if (params_data->buffer)
+ report_test_failure ("buffer set but failed sent!");
+
+ params_data->complete = false;
+ params_data->buffer = NULL;
+}
+
+static const struct zwp_linux_buffer_params_v1_listener params_listener =
+ {
+ handle_params_created,
+ handle_params_failed,
+ };
+
+
+
+/* Create a 200x200 buffer in some 32 bpp format. Fill it with three
+ colors: red, green, and blue. */
+
+static struct wl_buffer *
+create_rainbow_buffer (uint32_t format, uint64_t modifier,
+ uint32_t red_pixel, uint32_t green_pixel,
+ uint32_t blue_pixel)
+{
+ struct gbm_bo *buffer_object;
+ void *map_data;
+ char *buffer_data, *line;
+ uint32_t stride;
+ int i, fd;
+ struct zwp_linux_buffer_params_v1 *params;
+ struct test_params_data data;
+
+ /* map_data must be NULL when it is first given to gbm_bo_map. */
+ map_data = NULL;
+
+ if (!is_format_supported (format, modifier))
+ report_test_failure ("the specified format %8"PRIx32" with modifier"
+ " 0x%16"PRIx64" is not supported",
+ format, modifier);
+
+ if (modifier != DRM_FORMAT_MOD_INVALID)
+ buffer_object
+ = gbm_bo_create_with_modifiers2 (gbm_device, 500, 500, format,
+ &modifier, 1,
+ GBM_BO_USE_RENDERING);
+ else
+ buffer_object = gbm_bo_create (gbm_device, 500, 500, format,
+ GBM_BO_USE_RENDERING);
+
+ if (!buffer_object)
+ return NULL;
+
+ buffer_data = gbm_bo_map (buffer_object, 0, 0, 500, 500,
+ GBM_BO_TRANSFER_WRITE, &stride,
+ &map_data);
+
+ if (!buffer_data)
+ goto error_1;
+
+ line = malloc (stride);
+
+ if (!line)
+ goto error_2;
+
+ /* Fill the line with the red pixel, and then fill the first 166
+ rows with it. buffer_data may not be suitably aligned, so use
+ memcpy. */
+ for (i = 0; i < 500; ++i)
+ memcpy (line + i * 4, &red_pixel, sizeof red_pixel);
+
+ for (i = 0; i < 166; ++i)
+ memcpy (buffer_data + stride * i, line, 4 * 500);
+
+ /* Repeat with the green pixel. */
+ for (i = 0; i < 500; ++i)
+ memcpy (line + i * 4, &green_pixel, sizeof green_pixel);
+
+ for (i = 166; i < 332; ++i)
+ memcpy (buffer_data + stride * i, line, 4 * 500);
+
+ /* Finally with the blue pixel. */
+ for (i = 0; i < 500; ++i)
+ memcpy (line + i * 4, &blue_pixel, sizeof blue_pixel);
+
+ for (i = 332; i < 500; ++i)
+ memcpy (buffer_data + stride * i, line, 4 * 500);
+
+ free (line);
+
+ /* Now, export the buffer. */
+ fd = gbm_bo_get_fd (buffer_object);
+
+ if (fd < 1)
+ goto error_2;
+
+ params = zwp_linux_dmabuf_v1_create_params (linux_dmabuf_v1);
+
+ if (!params)
+ goto error_3;
+
+ zwp_linux_buffer_params_v1_add (params, fd, 0,
+ gbm_bo_get_offset (buffer_object, 0),
+ gbm_bo_get_stride (buffer_object),
+ modifier >> 32,
+ modifier & 0xffffffff);
+ zwp_linux_buffer_params_v1_create (params, 500, 500, format, 0);
+
+ /* Now, wait for either success or failure. */
+ zwp_linux_buffer_params_v1_add_listener (params, ¶ms_listener,
+ &data);
+ data.complete = false;
+ data.buffer = NULL;
+
+ while (!data.complete)
+ {
+ if (wl_display_dispatch (display->display) == -1)
+ die ("wl_display_dispatch");
+ }
+
+ if (!data.buffer)
+ goto error_4;
+
+ /* Otherwise, the buffer has been created. Return it now. */
+
+ zwp_linux_buffer_params_v1_destroy (params);
+ close (fd);
+ gbm_bo_unmap (buffer_object, map_data);
+ gbm_bo_destroy (buffer_object);
+
+ return data.buffer;
+
+ error_4:
+ zwp_linux_buffer_params_v1_destroy (params);
+ error_3:
+ close (fd);
+ error_2:
+ gbm_bo_unmap (buffer_object, map_data);
+ error_1:
+ gbm_bo_destroy (buffer_object);
+ return NULL;
+}
+
+
+
+static void
+run_test (void)
+{
+ if (!make_test_surface (display, &wayland_surface,
+ &test_surface))
+ report_test_failure ("failed to create test surface");
+
+ /* Open the test surface. */
+ open_surface ();
+
+ test_surface_add_listener (test_surface, &test_surface_listener,
+ NULL);
+ test_single_step (ARGB8888_KIND);
+
+ while (true)
+ {
+ if (wl_display_dispatch (display->display) == -1)
+ die ("wl_display_dispatch");
+ }
+}
+
+int
+main (void)
+{
+ test_init ();
+ display = open_test_display (test_interfaces,
+ ARRAYELTS (test_interfaces));
+
+ if (!display)
+ report_test_failure ("failed to open display");
+
+ run_test ();
+}