screencast: use dmabuf_feedback

The compositor can announce it's default rendering device via
linux_dmabuf_feedback as the main_device [1]. We should use this device
whenever possible. If aquiring this device fails we are adviced to use
force linear layout on buffers allocated with the implicit api.

With linux_dmabuf_v1 the modifier event is deprecated. Instead the
format_table event in combination with the tranches of
linux_dmabuf_feedback_v1 has to be used.

[1] https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/linux-dmabuf/feedback.rst
This commit is contained in:
columbarius 2022-05-05 23:41:53 +02:00
parent 3591fd2a6c
commit 2219db7508
4 changed files with 193 additions and 25 deletions

View file

@ -5,6 +5,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h> #include <spa/param/video/format-utils.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <xf86drm.h>
#include "fps_limit.h" #include "fps_limit.h"
@ -97,6 +98,12 @@ struct xdpw_format_modifier_pair {
uint64_t modifier; uint64_t modifier;
}; };
struct xdpw_dmabuf_feedback_data {
void *format_table_data;
uint32_t format_table_size;
bool device_used;
};
struct xdpw_screencast_context { struct xdpw_screencast_context {
// xdpw // xdpw
@ -113,6 +120,8 @@ struct xdpw_screencast_context {
struct zxdg_output_manager_v1 *xdg_output_manager; struct zxdg_output_manager_v1 *xdg_output_manager;
struct wl_shm *shm; struct wl_shm *shm;
struct zwp_linux_dmabuf_v1 *linux_dmabuf; struct zwp_linux_dmabuf_v1 *linux_dmabuf;
struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback;
struct xdpw_dmabuf_feedback_data feedback_data;
struct wl_list format_modifier_pairs; struct wl_list format_modifier_pairs;
// gbm // gbm
@ -173,7 +182,7 @@ struct xdpw_wlr_output {
}; };
void randname(char *buf); void randname(char *buf);
struct gbm_device *xdpw_gbm_device_create(void); struct gbm_device *xdpw_gbm_device_create(drmDevice *device);
struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast, struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast,
enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info); enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info);
void xdpw_buffer_destroy(struct xdpw_buffer *buffer); void xdpw_buffer_destroy(struct xdpw_buffer *buffer);

View file

@ -12,7 +12,8 @@
#define XDG_OUTPUT_MANAGER_VERSION 3 #define XDG_OUTPUT_MANAGER_VERSION 3
#define LINUX_DMABUF_VERSION 3 #define LINUX_DMABUF_VERSION 4
#define LINUX_DMABUF_VERSION_MIN 3
struct xdpw_state; struct xdpw_state;

View file

@ -7,7 +7,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <xf86drm.h>
#include "linux-dmabuf-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "logger.h" #include "logger.h"
@ -23,13 +22,16 @@ void randname(char *buf) {
} }
} }
static char *gbm_find_render_node() { static char *gbm_find_render_node(drmDevice *device) {
drmDevice *devices[64]; drmDevice *devices[64];
char *render_node = NULL; char *render_node = NULL;
int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0]));
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
drmDevice *dev = devices[i]; drmDevice *dev = devices[i];
if (device && !drmDevicesEqual(device, dev)) {
continue;
}
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue; continue;
@ -41,11 +43,11 @@ static char *gbm_find_render_node() {
return render_node; return render_node;
} }
struct gbm_device *xdpw_gbm_device_create(void) { struct gbm_device *xdpw_gbm_device_create(drmDevice *device) {
struct gbm_device *gbm; struct gbm_device *gbm;
char *render_node = NULL; char *render_node = NULL;
render_node = gbm_find_render_node(); render_node = gbm_find_render_node(device);
if (render_node == NULL) { if (render_node == NULL) {
logprint(ERROR, "xdpw: Could not find render node"); logprint(ERROR, "xdpw: Could not find render node");
return NULL; return NULL;

View file

@ -15,6 +15,7 @@
#include <unistd.h> #include <unistd.h>
#include <assert.h> #include <assert.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <xf86drm.h>
#include "screencast.h" #include "screencast.h"
#include "pipewire_screencast.h" #include "pipewire_screencast.h"
@ -545,6 +546,26 @@ static void wlr_remove_output(struct xdpw_wlr_output *out) {
free(out); free(out);
} }
static void wlr_format_modifier_pair_add(struct xdpw_screencast_context *ctx,
uint32_t format, uint64_t modifier) {
struct xdpw_format_modifier_pair *fm_pair = calloc(1, sizeof(struct xdpw_format_modifier_pair));
fm_pair->fourcc = format;
fm_pair->modifier = modifier;
logprint(TRACE, "wlroots: format %u (%lu)", fm_pair->fourcc, fm_pair->modifier);
wl_list_insert(&ctx->format_modifier_pairs, &fm_pair->link);
}
static void wlr_format_modifier_pair_emtpy_list(struct xdpw_screencast_context *ctx) {
struct xdpw_format_modifier_pair *fm_pair, *tmp;
wl_list_for_each_safe(fm_pair, tmp, &ctx->format_modifier_pairs, link) {
wl_list_remove(&fm_pair->link);
free(fm_pair);
}
}
static void linux_dmabuf_handle_modifier(void *data, static void linux_dmabuf_handle_modifier(void *data,
struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1,
uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) {
@ -552,14 +573,8 @@ static void linux_dmabuf_handle_modifier(void *data,
logprint(TRACE, "wlroots: linux_dmabuf_handle_modifier called"); logprint(TRACE, "wlroots: linux_dmabuf_handle_modifier called");
struct xdpw_format_modifier_pair *fm_pair = calloc(1, sizeof(struct xdpw_format_modifier_pair)); uint64_t modifier = (((uint64_t)modifier_hi) << 32) | modifier_lo;
wlr_format_modifier_pair_add(ctx, format, modifier);
fm_pair->fourcc = format;
fm_pair->modifier = ((((uint64_t)modifier_hi) << 32) | modifier_lo);
logprint(TRACE, "wlroots: format %u (%u)", fm_pair->fourcc, fm_pair->modifier);
wl_list_insert(&ctx->format_modifier_pairs, &fm_pair->link);
} }
static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = {
@ -567,6 +582,134 @@ static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = {
.modifier = linux_dmabuf_handle_modifier, .modifier = linux_dmabuf_handle_modifier,
}; };
static void linux_dmabuf_feedback_handle_main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device_arr) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_main_device called");
assert(ctx->gbm == NULL);
dev_t device;
assert(device_arr->size == sizeof(device));
memcpy(&device, device_arr->data, sizeof(device));
drmDevice *drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
logprint(WARN, "wlroots: unable to open main device");
ctx->state->config->screencast_conf.force_mod_linear = true;
return;
}
ctx->gbm = xdpw_gbm_device_create(drmDev);
}
static void linux_dmabuf_feedback_format_table(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int fd, uint32_t size) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_format_table called");
wlr_format_modifier_pair_emtpy_list(ctx);
ctx->feedback_data.format_table_data = mmap(NULL , size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ctx->feedback_data.format_table_data == MAP_FAILED) {
ctx->feedback_data.format_table_data = NULL;
ctx->feedback_data.format_table_size = 0;
return;
}
ctx->feedback_data.format_table_size = size;
}
static void linux_dmabuf_feedback_handle_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_done called");
if (ctx->feedback_data.format_table_data) {
munmap(ctx->feedback_data.format_table_data, ctx->feedback_data.format_table_size);
}
ctx->feedback_data.format_table_data = NULL;
ctx->feedback_data.format_table_size = 0;
}
static void linux_dmabuf_feedback_tranche_target_devices(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device_arr) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_target_devices called");
dev_t device;
assert(device_arr->size == sizeof(device));
memcpy(&device, device_arr->data, sizeof(device));
drmDevice *drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
return;
}
if (ctx->gbm) {
drmDevice *drmDevRenderer = NULL;
drmGetDevice2(gbm_device_get_fd(ctx->gbm), /* flags */ 0, &drmDevRenderer);
ctx->feedback_data.device_used = drmDevicesEqual(drmDevRenderer, drmDev);
} else {
ctx->gbm = xdpw_gbm_device_create(drmDev);
ctx->feedback_data.device_used = ctx->gbm != NULL;
}
}
static void linux_dmabuf_feedback_tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags) {
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_flags called");
}
static void linux_dmabuf_feedback_tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *indices) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_format_table called");
if (!ctx->feedback_data.device_used || !ctx->feedback_data.format_table_data) {
return;
}
struct fm_entry {
uint32_t format;
uint32_t padding;
uint64_t modifier;
};
// An entry in the table has to be 16 bytes long
assert(sizeof(struct fm_entry) == 16);
uint32_t n_modifiers = ctx->feedback_data.format_table_size/sizeof(struct fm_entry);
struct fm_entry *fm_entry = ctx->feedback_data.format_table_data;
uint16_t *idx;
wl_array_for_each(idx, indices) {
if (*idx >= n_modifiers) {
continue;
}
wlr_format_modifier_pair_add(ctx, (fm_entry + *idx)->format, (fm_entry + *idx)->modifier);
}
}
static void linux_dmabuf_feedback_tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) {
struct xdpw_screencast_context *ctx = data;
logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_done called");
ctx->feedback_data.device_used = false;
}
static const struct zwp_linux_dmabuf_feedback_v1_listener linux_dmabuf_listener_feedback = {
.main_device = linux_dmabuf_feedback_handle_main_device,
.format_table = linux_dmabuf_feedback_format_table,
.done = linux_dmabuf_feedback_handle_done,
.tranche_target_device = linux_dmabuf_feedback_tranche_target_devices,
.tranche_flags = linux_dmabuf_feedback_tranche_flags,
.tranche_formats = linux_dmabuf_feedback_tranche_formats,
.tranche_done = linux_dmabuf_feedback_tranche_done,
};
static void wlr_registry_handle_add(void *data, struct wl_registry *reg, static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
uint32_t id, const char *interface, uint32_t ver) { uint32_t id, const char *interface, uint32_t ver) {
struct xdpw_screencast_context *ctx = data; struct xdpw_screencast_context *ctx = data;
@ -609,10 +752,22 @@ static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
wl_registry_bind(reg, id, &zxdg_output_manager_v1_interface, XDG_OUTPUT_MANAGER_VERSION); wl_registry_bind(reg, id, &zxdg_output_manager_v1_interface, XDG_OUTPUT_MANAGER_VERSION);
} }
if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) { if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, LINUX_DMABUF_VERSION); uint32_t version = ver;
ctx->linux_dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, LINUX_DMABUF_VERSION); if (LINUX_DMABUF_VERSION < ver) {
version = LINUX_DMABUF_VERSION;
} else if (LINUX_DMABUF_VERSION_MIN > ver) {
logprint(INFO, "wlroots: interface %s (Version %u) is required for DMA-BUF screencast", interface, LINUX_DMABUF_VERSION_MIN);
return;
}
logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version);
ctx->linux_dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, version);
zwp_linux_dmabuf_v1_add_listener(ctx->linux_dmabuf, &linux_dmabuf_listener, ctx); if (version >= 4) {
ctx->linux_dmabuf_feedback = zwp_linux_dmabuf_v1_get_default_feedback(ctx->linux_dmabuf);
zwp_linux_dmabuf_feedback_v1_add_listener(ctx->linux_dmabuf_feedback, &linux_dmabuf_listener_feedback, ctx);
} else {
zwp_linux_dmabuf_v1_add_listener(ctx->linux_dmabuf, &linux_dmabuf_listener, ctx);
}
} }
} }
@ -677,20 +832,18 @@ int xdpw_wlr_screencopy_init(struct xdpw_state *state) {
} }
// make sure we have a gbm device // make sure we have a gbm device
ctx->gbm = xdpw_gbm_device_create(); if (ctx->linux_dmabuf && !ctx->gbm) {
if (!ctx->gbm) { ctx->gbm = xdpw_gbm_device_create(NULL);
logprint(ERROR, "System doesn't support gbm!"); if (!ctx->gbm) {
logprint(ERROR, "System doesn't support gbm!");
}
} }
return 0; return 0;
} }
void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) { void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) {
struct xdpw_format_modifier_pair *fm_pair, *tmp_fmp; wlr_format_modifier_pair_emtpy_list(ctx);
wl_list_for_each_safe(fm_pair, tmp_fmp, &ctx->format_modifier_pairs, link) {
wl_list_remove(&fm_pair->link);
free(fm_pair);
}
struct xdpw_wlr_output *output, *tmp_o; struct xdpw_wlr_output *output, *tmp_o;
wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
@ -718,6 +871,9 @@ void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) {
gbm_device_destroy(ctx->gbm); gbm_device_destroy(ctx->gbm);
close(fd); close(fd);
} }
if (ctx->linux_dmabuf_feedback) {
zwp_linux_dmabuf_feedback_v1_destroy(ctx->linux_dmabuf_feedback);
}
if (ctx->linux_dmabuf) { if (ctx->linux_dmabuf) {
zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf); zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf);
} }