backend/wayland: add basic linux-dmabuf feedback support

This patch makes it so we bind to zwp_linux_dmabuf_v1 version 4 and
we use it to grab the main device. v4 sends supported formats via a
table so we need to handle this as well.

v4 allows wlroots to remove the requirement for Mesa's internal
wl_drm interface.
This commit is contained in:
Simon Ser 2021-12-08 01:43:15 +01:00 committed by Kirill Primak
parent 8e566f716c
commit 9f41627aa1
2 changed files with 162 additions and 6 deletions

View File

@ -4,6 +4,8 @@
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <drm_fourcc.h>
@ -30,6 +32,21 @@
#include "tablet-unstable-v2-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
struct wlr_wl_linux_dmabuf_feedback_v1 {
struct wlr_wl_backend *backend;
dev_t main_device_id;
struct wlr_wl_linux_dmabuf_v1_table_entry *format_table;
size_t format_table_size;
dev_t tranche_target_device_id;
};
struct wlr_wl_linux_dmabuf_v1_table_entry {
uint32_t format;
uint32_t pad; /* unused */
uint64_t modifier;
};
struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend) {
assert(wlr_backend_is_wl(backend));
return (struct wlr_wl_backend *)backend;
@ -108,6 +125,119 @@ static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_v1_listener = {
.modifier = linux_dmabuf_v1_handle_modifier,
};
static void linux_dmabuf_feedback_v1_handle_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback) {
// This space is intentionally left blank
}
static void linux_dmabuf_feedback_v1_handle_format_table(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback, int fd, uint32_t size) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
feedback_data->format_table = NULL;
void *table_data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (table_data == MAP_FAILED) {
wlr_log_errno(WLR_ERROR, "failed to mmap DMA-BUF format table");
} else {
feedback_data->format_table = table_data;
feedback_data->format_table_size = size;
}
close(fd);
}
static void linux_dmabuf_feedback_v1_handle_main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *dev_id_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
dev_t dev_id;
assert(dev_id_arr->size == sizeof(dev_id));
memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id));
feedback_data->main_device_id = dev_id;
drmDevice *device = NULL;
if (drmGetDeviceFromDevId(dev_id, 0, &device) != 0) {
wlr_log_errno(WLR_ERROR, "drmGetDeviceFromDevId failed");
return;
}
const char *name = NULL;
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
name = device->nodes[DRM_NODE_RENDER];
} else {
// Likely a split display/render setup. Pick the primary node and hope
// Mesa will open the right render node under-the-hood.
assert(device->available_nodes & (1 << DRM_NODE_PRIMARY));
name = device->nodes[DRM_NODE_PRIMARY];
wlr_log(WLR_DEBUG, "DRM device %s has no render node, "
"falling back to primary node", name);
}
feedback_data->backend->drm_render_name = strdup(name);
drmFreeDevice(&device);
}
static void linux_dmabuf_feedback_v1_handle_tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
feedback_data->tranche_target_device_id = 0;
}
static void linux_dmabuf_feedback_v1_handle_tranche_target_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *dev_id_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
dev_t dev_id;
assert(dev_id_arr->size == sizeof(dev_id));
memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id));
feedback_data->tranche_target_device_id = dev_id;
}
static void linux_dmabuf_feedback_v1_handle_tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *indices_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
if (feedback_data->format_table == NULL) {
return;
}
if (feedback_data->tranche_target_device_id != feedback_data->main_device_id) {
return;
}
size_t table_cap = feedback_data->format_table_size /
sizeof(struct wlr_wl_linux_dmabuf_v1_table_entry);
uint16_t *index_ptr;
wl_array_for_each(index_ptr, indices_arr) {
assert(*index_ptr < table_cap);
const struct wlr_wl_linux_dmabuf_v1_table_entry *entry =
&feedback_data->format_table[*index_ptr];
wlr_drm_format_set_add(&feedback_data->backend->linux_dmabuf_v1_formats,
entry->format, entry->modifier);
}
}
static void linux_dmabuf_feedback_v1_handle_tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback, uint32_t flags) {
// TODO: handle SCANOUT flag
}
static const struct zwp_linux_dmabuf_feedback_v1_listener
linux_dmabuf_feedback_v1_listener = {
.done = linux_dmabuf_feedback_v1_handle_done,
.format_table = linux_dmabuf_feedback_v1_handle_format_table,
.main_device = linux_dmabuf_feedback_v1_handle_main_device,
.tranche_done = linux_dmabuf_feedback_v1_handle_tranche_done,
.tranche_target_device = linux_dmabuf_feedback_v1_handle_tranche_target_device,
.tranche_formats = linux_dmabuf_feedback_v1_handle_tranche_formats,
.tranche_flags = linux_dmabuf_feedback_v1_handle_tranche_flags,
};
static bool device_has_name(const drmDevice *device, const char *name) {
for (size_t i = 0; i < DRM_NODE_MAX; i++) {
if (!(device->available_nodes & (1 << i))) {
@ -172,8 +302,6 @@ static char *get_render_name(const char *name) {
static void legacy_drm_handle_device(void *data, struct wl_drm *drm,
const char *name) {
struct wlr_wl_backend *wl = data;
// TODO: get FD from linux-dmabuf hints instead
wl->drm_render_name = get_render_name(name);
}
@ -245,7 +373,7 @@ static void registry_global(void *data, struct wl_registry *registry,
} else if (strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0 &&
version >= 3) {
wl->zwp_linux_dmabuf_v1 = wl_registry_bind(registry, name,
&zwp_linux_dmabuf_v1_interface, 3);
&zwp_linux_dmabuf_v1_interface, version >= 4 ? 4 : version);
zwp_linux_dmabuf_v1_add_listener(wl->zwp_linux_dmabuf_v1,
&linux_dmabuf_v1_listener, wl);
} else if (strcmp(iface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
@ -428,10 +556,9 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_display *display,
wlr_log_errno(WLR_ERROR, "Could not obtain reference to remote registry");
goto error_display;
}
wl_registry_add_listener(wl->registry, &registry_listener, wl);
wl_display_roundtrip(wl->remote_display); // get globals
wl_display_roundtrip(wl->remote_display); // get linux-dmabuf formats
if (!wl->compositor) {
wlr_log(WLR_ERROR,
@ -444,6 +571,35 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_display *display,
goto error_registry;
}
struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback_v1 = NULL;
struct wlr_wl_linux_dmabuf_feedback_v1 feedback_data = { .backend = wl };
if (wl->zwp_linux_dmabuf_v1 != NULL &&
zwp_linux_dmabuf_v1_get_version(wl->zwp_linux_dmabuf_v1) >=
ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) {
linux_dmabuf_feedback_v1 =
zwp_linux_dmabuf_v1_get_default_feedback(wl->zwp_linux_dmabuf_v1);
if (linux_dmabuf_feedback_v1 == NULL) {
wlr_log(WLR_ERROR, "Allocation failed");
goto error_registry;
}
zwp_linux_dmabuf_feedback_v1_add_listener(linux_dmabuf_feedback_v1,
&linux_dmabuf_feedback_v1_listener, &feedback_data);
if (wl->legacy_drm != NULL) {
wl_drm_destroy(wl->legacy_drm);
wl->legacy_drm = NULL;
}
}
wl_display_roundtrip(wl->remote_display); // get linux-dmabuf formats
if (feedback_data.format_table != NULL) {
munmap(feedback_data.format_table, feedback_data.format_table_size);
}
if (linux_dmabuf_feedback_v1 != NULL) {
zwp_linux_dmabuf_feedback_v1_destroy(linux_dmabuf_feedback_v1);
}
struct wl_event_loop *loop = wl_display_get_event_loop(wl->local_display);
int fd = wl_display_get_fd(wl->remote_display);
wl->remote_display_src = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE,

View File

@ -105,7 +105,7 @@ wayland_server = dependency('wayland-server',
)
drm = dependency('libdrm',
version: '>=2.4.108',
version: '>=2.4.109',
fallback: ['libdrm', 'ext_libdrm'],
default_options: [
'libkms=false',