/* 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 ();
}