egl/wayland: add initial dma-buf feedback support

This bumps the supported dma-buf version up to 4 and adds the initial
dma-buf feedback implementation. It follows the changes in the dma-buf
protocol extension [1] to include the dma-buf feedback interface, which
should be incorporated by most Wayland compositors in the future.

From version 4 onwards, the dma-buf modifier events are not sent by the
compositor anymore, so we use the default feedback to pick the set of
formats/modifiers supported by the compositor. Also, we try to avoid the
wl_drm device event and instead use the dma-buf feedback main device. We
only fallback to wl_drm when the compositor advertises a device that
does not have a render node associated.

In this initial dma-buf feedback implementation we still don't do
anything with the per-surface dma-buf feedback, but in the next commits
we add proper support.

It's important to mention that this also bumps the minimal supported
version of wayland-protocols to 1.24, in order to include [1].

[1] https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/8

This patch is based on previous work of Scott Anderson (@ascent).

Signed-off-by: Scott Anderson <scott.anderson@collabora.com>
Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/11248>
This commit is contained in:
Leandro Ribeiro 2021-11-15 13:51:12 -03:00 committed by Marge Bot
parent a25d4dd276
commit 83916ae0b9
3 changed files with 453 additions and 13 deletions

View File

@ -1990,7 +1990,7 @@ if with_platform_wayland
else
wl_scanner_arg = 'code'
endif
dep_wl_protocols = dependency('wayland-protocols', version : '>= 1.8')
dep_wl_protocols = dependency('wayland-protocols', version : '>= 1.24')
dep_wayland_client = dependency('wayland-client', version : '>=1.18')
dep_wayland_server = dependency('wayland-server', version : '>=1.18')
if with_egl

View File

@ -53,6 +53,7 @@ struct wl_registry;
struct wl_shm;
struct wl_surface;
struct zwp_linux_dmabuf_v1;
struct zwp_linux_dmabuf_feedback_v1;
#endif
#include <GL/gl.h>
@ -87,6 +88,7 @@ struct zwp_linux_dmabuf_v1;
#include "eglsync.h"
#include "util/u_vector.h"
#include "util/u_dynarray.h"
#include "util/bitset.h"
struct wl_buffer;
@ -181,6 +183,28 @@ struct dri2_wl_formats {
/* Array of vectors. Contains one modifier vector per format */
struct u_vector *modifiers;
};
struct dmabuf_feedback_format_table {
unsigned int size;
struct {
uint32_t format;
uint32_t padding; /* unused */
uint64_t modifier;
} *data;
};
struct dmabuf_feedback_tranche {
dev_t target_device;
uint32_t flags;
struct dri2_wl_formats formats;
};
struct dmabuf_feedback {
dev_t main_device;
struct dmabuf_feedback_format_table format_table;
struct util_dynarray tranches;
struct dmabuf_feedback_tranche pending_tranche;
};
#endif
struct dri2_egl_display
@ -251,10 +275,13 @@ struct dri2_egl_display
struct wl_registry *wl_registry;
struct wl_drm *wl_server_drm;
struct wl_drm *wl_drm;
uint32_t wl_drm_version, wl_drm_name;
struct wl_shm *wl_shm;
struct wl_event_queue *wl_queue;
struct zwp_linux_dmabuf_v1 *wl_dmabuf;
struct dri2_wl_formats formats;
struct zwp_linux_dmabuf_feedback_v1 *wl_dmabuf_feedback;
struct dmabuf_feedback_format_table format_table;
bool authenticated;
char *device_name;
#endif
@ -300,6 +327,9 @@ struct dri2_egl_surface
struct wl_display *wl_dpy_wrapper;
struct wl_drm *wl_drm_wrapper;
struct wl_callback *throttle_callback;
struct zwp_linux_dmabuf_feedback_v1 *wl_dmabuf_feedback;
struct dmabuf_feedback dmabuf_feedback, pending_dmabuf_feedback;
bool compositor_using_another_device;
int format;
bool resized;
#endif

View File

@ -318,6 +318,64 @@ err:
return -1;
}
static void
dmabuf_feedback_format_table_fini(struct dmabuf_feedback_format_table *format_table)
{
if (format_table->data && format_table->data != MAP_FAILED)
munmap(format_table->data, format_table->size);
}
static void
dmabuf_feedback_format_table_init(struct dmabuf_feedback_format_table *format_table)
{
memset(format_table, 0, sizeof(*format_table));
}
static void
dmabuf_feedback_tranche_fini(struct dmabuf_feedback_tranche *tranche)
{
dri2_wl_formats_fini(&tranche->formats);
}
static int
dmabuf_feedback_tranche_init(struct dmabuf_feedback_tranche *tranche)
{
memset(tranche, 0, sizeof(*tranche));
if (dri2_wl_formats_init(&tranche->formats) < 0)
return -1;
return 0;
}
static void
dmabuf_feedback_fini(struct dmabuf_feedback *dmabuf_feedback)
{
dmabuf_feedback_tranche_fini(&dmabuf_feedback->pending_tranche);
util_dynarray_foreach(&dmabuf_feedback->tranches,
struct dmabuf_feedback_tranche, tranche)
dmabuf_feedback_tranche_fini(tranche);
util_dynarray_fini(&dmabuf_feedback->tranches);
dmabuf_feedback_format_table_fini(&dmabuf_feedback->format_table);
}
static int
dmabuf_feedback_init(struct dmabuf_feedback *dmabuf_feedback)
{
memset(dmabuf_feedback, 0, sizeof(*dmabuf_feedback));
if (dmabuf_feedback_tranche_init(&dmabuf_feedback->pending_tranche) < 0)
return -1;
util_dynarray_init(&dmabuf_feedback->tranches, NULL);
dmabuf_feedback_format_table_init(&dmabuf_feedback->format_table);
return 0;
}
static void
resize_callback(struct wl_egl_window *wl_win, void *data)
{
@ -365,6 +423,157 @@ get_wl_surface_proxy(struct wl_egl_window *window)
return wl_proxy_create_wrapper(window->surface);
}
static void
surface_dmabuf_feedback_format_table(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
int32_t fd, uint32_t size)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
feedback->format_table.size = size;
feedback->format_table.data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
}
static void
surface_dmabuf_feedback_main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *device)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
memcpy(&feedback->main_device, device->data, sizeof(feedback->main_device));
/* Compositors may support switching render devices and change the main
* device of the dma-buf feedback. In this case, when we reallocate the
* buffers of the surface we must ensure that it is not allocated in memory
* that is only visible to the GPU that EGL is using, as the compositor will
* have to import them to the render device it is using.
*
* TODO: we still don't know how to allocate such buffers.
*/
if (dri2_surf->dmabuf_feedback.main_device != 0 &&
(feedback->main_device != dri2_surf->dmabuf_feedback.main_device))
dri2_surf->compositor_using_another_device = true;
else
dri2_surf->compositor_using_another_device = false;
}
static void
surface_dmabuf_feedback_tranche_target_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *device)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
memcpy(&feedback->pending_tranche.target_device, device->data,
sizeof(feedback->pending_tranche.target_device));
}
static void
surface_dmabuf_feedback_tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
uint32_t flags)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
feedback->pending_tranche.flags = flags;
}
static void
surface_dmabuf_feedback_tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *indices)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
uint64_t *modifier_ptr, modifier;
uint32_t format;
uint16_t *index;
int visual_idx;
/* Compositor may advertise or not a format table. If it does, we use it.
* Otherwise, we steal the most recent advertised format table. If we don't have
* a most recent advertised format table, compositor did something wrong. */
if (feedback->format_table.data == NULL) {
feedback->format_table = dri2_surf->dmabuf_feedback.format_table;
dmabuf_feedback_format_table_init(&dri2_surf->dmabuf_feedback.format_table);
}
if (feedback->format_table.data == MAP_FAILED) {
_eglLog(_EGL_WARNING, "wayland-egl: we could not map the format table "
"so we won't be able to use this batch of dma-buf "
"feedback events.");
return;
}
if (feedback->format_table.data == NULL) {
_eglLog(_EGL_WARNING, "wayland-egl: compositor didn't advertise a format "
"table, so we won't be able to use this batch of dma-buf "
"feedback events.");
return;
}
wl_array_for_each(index, indices) {
format = feedback->format_table.data[*index].format;
modifier = feedback->format_table.data[*index].modifier;
/* Skip formats that are not the one the surface is already using. We
* can't switch to another format. */
if (format != dri2_surf->format)
continue;
/* We are sure that the format is supported because of the check above. */
visual_idx = dri2_wl_visual_idx_from_fourcc(format);
assert(visual_idx != -1);
BITSET_SET(feedback->pending_tranche.formats.formats_bitmap, visual_idx);
modifier_ptr =
u_vector_add(&feedback->pending_tranche.formats.modifiers[visual_idx]);
if (modifier_ptr)
*modifier_ptr = modifier;
}
}
static void
surface_dmabuf_feedback_tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback)
{
struct dri2_egl_surface *dri2_surf = data;
struct dmabuf_feedback *feedback = &dri2_surf->pending_dmabuf_feedback;
/* Add tranche to array of tranches. */
util_dynarray_append(&feedback->tranches, struct dmabuf_feedback_tranche,
feedback->pending_tranche);
dmabuf_feedback_tranche_init(&feedback->pending_tranche);
}
static void
surface_dmabuf_feedback_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback)
{
struct dri2_egl_surface *dri2_surf = data;
dmabuf_feedback_fini(&dri2_surf->dmabuf_feedback);
dri2_surf->dmabuf_feedback = dri2_surf->pending_dmabuf_feedback;
dmabuf_feedback_init(&dri2_surf->pending_dmabuf_feedback);
}
static const struct zwp_linux_dmabuf_feedback_v1_listener
surface_dmabuf_feedback_listener = {
.format_table = surface_dmabuf_feedback_format_table,
.main_device = surface_dmabuf_feedback_main_device,
.tranche_target_device = surface_dmabuf_feedback_tranche_target_device,
.tranche_flags = surface_dmabuf_feedback_tranche_flags,
.tranche_formats = surface_dmabuf_feedback_tranche_formats,
.tranche_done = surface_dmabuf_feedback_tranche_done,
.done = surface_dmabuf_feedback_done,
};
/**
* Called via eglCreateWindowSurface(), drv->CreateWindowSurface().
*/
@ -376,6 +585,7 @@ dri2_wl_create_window_surface(_EGLDisplay *disp, _EGLConfig *conf,
struct dri2_egl_config *dri2_conf = dri2_egl_config(conf);
struct wl_egl_window *window = native_window;
struct dri2_egl_surface *dri2_surf;
struct zwp_linux_dmabuf_v1 *dmabuf_wrapper;
int visual_idx;
const __DRIconfig *config;
@ -487,6 +697,33 @@ dri2_wl_create_window_surface(_EGLDisplay *disp, _EGLConfig *conf,
wl_proxy_set_queue((struct wl_proxy *)dri2_surf->wl_surface_wrapper,
dri2_surf->wl_queue);
if (dri2_dpy->wl_dmabuf && zwp_linux_dmabuf_v1_get_version(dri2_dpy->wl_dmabuf) >=
ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION) {
dmabuf_wrapper = wl_proxy_create_wrapper(dri2_dpy->wl_dmabuf);
if (!dmabuf_wrapper) {
_eglError(EGL_BAD_ALLOC, "dri2_create_surface");
goto cleanup_surf_wrapper;
}
wl_proxy_set_queue((struct wl_proxy *)dmabuf_wrapper,
dri2_surf->wl_queue);
dri2_surf->wl_dmabuf_feedback =
zwp_linux_dmabuf_v1_get_surface_feedback(dmabuf_wrapper,
dri2_surf->wl_surface_wrapper);
wl_proxy_wrapper_destroy(dmabuf_wrapper);
zwp_linux_dmabuf_feedback_v1_add_listener(dri2_surf->wl_dmabuf_feedback,
&surface_dmabuf_feedback_listener,
dri2_surf);
if (dmabuf_feedback_init(&dri2_surf->pending_dmabuf_feedback) < 0)
goto cleanup_surf_wrapper;
if (dmabuf_feedback_init(&dri2_surf->dmabuf_feedback) < 0)
goto cleanup_pending_dmabuf_feedback;
if (roundtrip(dri2_dpy) < 0)
goto cleanup_dmabuf_feedback;
}
dri2_surf->wl_win = window;
dri2_surf->wl_win->driver_private = dri2_surf;
dri2_surf->wl_win->destroy_window_callback = destroy_window_callback;
@ -494,12 +731,19 @@ dri2_wl_create_window_surface(_EGLDisplay *disp, _EGLConfig *conf,
dri2_surf->wl_win->resize_callback = resize_callback;
if (!dri2_create_drawable(dri2_dpy, config, dri2_surf, dri2_surf))
goto cleanup_surf_wrapper;
goto cleanup_dmabuf_feedback;
dri2_surf->base.SwapInterval = dri2_dpy->default_swap_interval;
return &dri2_surf->base;
cleanup_dmabuf_feedback:
if (dri2_surf->wl_dmabuf_feedback) {
zwp_linux_dmabuf_feedback_v1_destroy(dri2_surf->wl_dmabuf_feedback);
dmabuf_feedback_fini(&dri2_surf->dmabuf_feedback);
cleanup_pending_dmabuf_feedback:
dmabuf_feedback_fini(&dri2_surf->pending_dmabuf_feedback);
}
cleanup_surf_wrapper:
wl_proxy_wrapper_destroy(dri2_surf->wl_surface_wrapper);
cleanup_dpy_wrapper:
@ -569,6 +813,11 @@ dri2_wl_destroy_surface(_EGLDisplay *disp, _EGLSurface *surf)
wl_proxy_wrapper_destroy(dri2_surf->wl_dpy_wrapper);
if (dri2_surf->wl_drm_wrapper)
wl_proxy_wrapper_destroy(dri2_surf->wl_drm_wrapper);
if (dri2_surf->wl_dmabuf_feedback) {
zwp_linux_dmabuf_feedback_v1_destroy(dri2_surf->wl_dmabuf_feedback);
dmabuf_feedback_fini(&dri2_surf->dmabuf_feedback);
dmabuf_feedback_fini(&dri2_surf->pending_dmabuf_feedback);
}
wl_event_queue_destroy(dri2_surf->wl_queue);
dri2_fini_surface(surf);
@ -1382,6 +1631,12 @@ dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *dmabuf,
int visual_idx = dri2_wl_visual_idx_from_fourcc(format);
uint64_t *mod;
/* Ignore this if the compositor advertised dma-buf feedback. From version 4
* onwards (when dma-buf feedback was introduced), the compositor should not
* advertise this event anymore, but let's keep this for safety. */
if (dri2_dpy->wl_dmabuf_feedback)
return;
if (visual_idx == -1)
return;
@ -1397,6 +1652,134 @@ static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
.modifier = dmabuf_handle_modifier,
};
static void
wl_drm_bind(struct dri2_egl_display *dri2_dpy)
{
dri2_dpy->wl_drm = wl_registry_bind(dri2_dpy->wl_registry, dri2_dpy->wl_drm_name,
&wl_drm_interface, dri2_dpy->wl_drm_version);
wl_drm_add_listener(dri2_dpy->wl_drm, &drm_listener, dri2_dpy);
}
static void
default_dmabuf_feedback_format_table(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
int32_t fd, uint32_t size)
{
struct dri2_egl_display *dri2_dpy = data;
dri2_dpy->format_table.size = size;
dri2_dpy->format_table.data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
}
static void
default_dmabuf_feedback_main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *device)
{
struct dri2_egl_display *dri2_dpy = data;
char *node;
int fd;
dev_t dev;
/* Given the device, look for a render node and try to open it. */
memcpy(&dev, device->data, sizeof(dev));
node = loader_get_render_node(dev);
if (!node)
return;
fd = loader_open_device(node);
if (fd == -1) {
free(node);
return;
}
dri2_dpy->device_name = node;
dri2_dpy->fd = fd;
dri2_dpy->authenticated = true;
}
static void
default_dmabuf_feedback_tranche_target_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *device)
{
/* ignore this event */
}
static void
default_dmabuf_feedback_tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
uint32_t flags)
{
/* ignore this event */
}
static void
default_dmabuf_feedback_tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback,
struct wl_array *indices)
{
struct dri2_egl_display *dri2_dpy = data;
uint64_t *modifier_ptr, modifier;
uint32_t format;
uint16_t *index;
int visual_idx;
if (dri2_dpy->format_table.data == MAP_FAILED) {
_eglLog(_EGL_WARNING, "wayland-egl: we could not map the format table "
"so we won't be able to use this batch of dma-buf "
"feedback events.");
return;
}
if (dri2_dpy->format_table.data == NULL) {
_eglLog(_EGL_WARNING, "wayland-egl: compositor didn't advertise a format "
"table, so we won't be able to use this batch of dma-buf "
"feedback events.");
return;
}
wl_array_for_each(index, indices) {
format = dri2_dpy->format_table.data[*index].format;
modifier = dri2_dpy->format_table.data[*index].modifier;
/* skip formats that we don't support */
visual_idx = dri2_wl_visual_idx_from_fourcc(format);
if (visual_idx == -1)
continue;
BITSET_SET(dri2_dpy->formats.formats_bitmap, visual_idx);
modifier_ptr = u_vector_add(&dri2_dpy->formats.modifiers[visual_idx]);
if (modifier_ptr)
*modifier_ptr = modifier;
}
}
static void
default_dmabuf_feedback_tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback)
{
/* ignore this event */
}
static void
default_dmabuf_feedback_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback)
{
/* ignore this event */
}
static const struct zwp_linux_dmabuf_feedback_v1_listener
dmabuf_feedback_listener = {
.format_table = default_dmabuf_feedback_format_table,
.main_device = default_dmabuf_feedback_main_device,
.tranche_target_device = default_dmabuf_feedback_tranche_target_device,
.tranche_flags = default_dmabuf_feedback_tranche_flags,
.tranche_formats = default_dmabuf_feedback_tranche_formats,
.tranche_done = default_dmabuf_feedback_tranche_done,
.done = default_dmabuf_feedback_done,
};
static void
registry_handle_global_drm(void *data, struct wl_registry *registry,
uint32_t name, const char *interface,
@ -1405,13 +1788,12 @@ registry_handle_global_drm(void *data, struct wl_registry *registry,
struct dri2_egl_display *dri2_dpy = data;
if (strcmp(interface, "wl_drm") == 0) {
dri2_dpy->wl_drm =
wl_registry_bind(registry, name, &wl_drm_interface, MIN2(version, 2));
wl_drm_add_listener(dri2_dpy->wl_drm, &drm_listener, dri2_dpy);
dri2_dpy->wl_drm_version = MIN2(version, 2);
dri2_dpy->wl_drm_name = name;
} else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0 && version >= 3) {
dri2_dpy->wl_dmabuf =
wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface,
MIN2(version, 3));
MIN2(version, ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION));
zwp_linux_dmabuf_v1_add_listener(dri2_dpy->wl_dmabuf, &dmabuf_listener,
dri2_dpy);
}
@ -1582,18 +1964,46 @@ dri2_initialize_wayland_drm(_EGLDisplay *disp)
dri2_dpy->wl_registry = wl_display_get_registry(dri2_dpy->wl_dpy_wrapper);
wl_registry_add_listener(dri2_dpy->wl_registry,
&registry_listener_drm, dri2_dpy);
/* The compositor must expose the dma-buf interface. */
if (roundtrip(dri2_dpy) < 0 || dri2_dpy->wl_dmabuf == NULL)
goto cleanup;
/* Get default dma-buf feedback */
if (zwp_linux_dmabuf_v1_get_version(dri2_dpy->wl_dmabuf) >=
ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) {
dmabuf_feedback_format_table_init(&dri2_dpy->format_table);
dri2_dpy->wl_dmabuf_feedback =
zwp_linux_dmabuf_v1_get_default_feedback(dri2_dpy->wl_dmabuf);
zwp_linux_dmabuf_feedback_v1_add_listener(dri2_dpy->wl_dmabuf_feedback,
&dmabuf_feedback_listener, dri2_dpy);
}
/* Receive events from the interfaces */
if (roundtrip(dri2_dpy) < 0)
goto cleanup;
if (dri2_dpy->wl_drm == NULL || dri2_dpy->wl_dmabuf == NULL)
goto cleanup;
/* Destroy the default dma-buf feedback and the format table. */
if (dri2_dpy->wl_dmabuf_feedback) {
zwp_linux_dmabuf_feedback_v1_destroy(dri2_dpy->wl_dmabuf_feedback);
dri2_dpy->wl_dmabuf_feedback = NULL;
dmabuf_feedback_format_table_fini(&dri2_dpy->format_table);
}
if (roundtrip(dri2_dpy) < 0 || dri2_dpy->fd == -1)
goto cleanup;
/* We couldn't retrieve a render node from the dma-buf feedback (or the
* feedback was not advertised at all), so we must fallback to wl_drm. */
if (dri2_dpy->fd == -1) {
wl_drm_bind(dri2_dpy);
if (!dri2_dpy->authenticated &&
(roundtrip(dri2_dpy) < 0 || !dri2_dpy->authenticated))
goto cleanup;
if (dri2_dpy->wl_drm == NULL)
goto cleanup;
if (roundtrip(dri2_dpy) < 0 || dri2_dpy->fd == -1)
goto cleanup;
if (!dri2_dpy->authenticated &&
(roundtrip(dri2_dpy) < 0 || !dri2_dpy->authenticated))
goto cleanup;
}
dri2_dpy->fd = loader_get_user_preferred_fd(dri2_dpy->fd,
&dri2_dpy->is_different_gpu);