Commit 362679d2 authored by Manuel Stoeckl's avatar Manuel Stoeckl
Browse files

Apply dmabuf modifiers to replicated dmabuf file descriptors

The code is complicated by the fact that the libgbm backends
sometimes don't support buffer combinations which compositors do,
and don't have an api to create buffer objects from the minimum
information required by the kernel.

This patch has only been tested with i915 drivers and x-tiled
surfaces.
parent 1eaec59c
......@@ -50,8 +50,9 @@ int init_render_data(struct render_data *data)
// Silent return, idempotent
return 0;
}
// todo: make this a command line option
const char card[] = "/dev/dri/renderD128";
// Try 1: DRM dumb buffers
int drm_fd = open(card, O_RDWR | O_CLOEXEC);
if (drm_fd == -1) {
wp_log(WP_ERROR, "Failed to open drm fd for %s: %s", card,
......@@ -82,7 +83,8 @@ void cleanup_render_data(struct render_data *data)
}
}
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size)
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size,
struct dmabuf_slice_data *info)
{
ssize_t endp = lseek(fd, 0, SEEK_END);
if (endp == -1) {
......@@ -97,19 +99,38 @@ struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size)
return NULL;
}
*size = (size_t)endp;
/* TODO: delay this invocation until we parse the protocol and are given
* metadata. Note: for the multiplanar buffers, this would, in the worst
* case, require that we block or reorder the 'add_fd' calls until
* someone calls create. Also, how are the multiplanar/multifd formats
* mapped? */
struct gbm_import_fd_data data;
data.fd = fd;
data.width = 256;
data.stride = 1024;
data.height = (uint32_t)(endp + 1023) / 1024;
data.format = GBM_FORMAT_XRGB8888;
struct gbm_bo *bo = gbm_bo_import(
rd->dev, GBM_BO_IMPORT_FD, &data, GBM_BO_USE_RENDERING);
/* Multiplanar formats are all rather badly supported by
* drivers/libgbm/libdrm/compositors/applications/everything. */
struct gbm_import_fd_modifier_data data;
data.num_fds = 1;
data.fds[0] = fd;
if (info && info->num_planes == 1) {
// Assume only one plane contains contents? otherwise fall
// back....
data.modifier = info->modifier;
int which = 0;
for (int i = 0; i < 4; i++) {
if (info->using_planes[i]) {
which = i;
}
}
data.strides[0] = (int)info->strides[which];
data.offsets[0] = (int)info->offsets[which];
data.width = info->width;
data.height = info->height;
data.format = info->format;
} else {
data.offsets[0] = 0;
data.strides[0] = 1024;
data.width = 256;
data.height = (uint32_t)(endp + 1023) / 1024;
data.format = GBM_FORMAT_XRGB8888;
data.modifier = 0;
}
struct gbm_bo *bo = gbm_bo_import(rd->dev, GBM_BO_IMPORT_FD_MODIFIER,
&data, GBM_BO_USE_RENDERING);
if (!bo) {
wp_log(WP_ERROR, "Failed to import dmabuf to gbm bo",
strerror(errno));
......@@ -119,29 +140,6 @@ struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size)
return bo;
}
int read_dmabuf(int fd, void *mapptr, size_t size, void *destination)
{
// Assuming mmap worked
if (!mapptr) {
wp_log(WP_ERROR, "dmabuf to read was null");
return -1;
}
struct dma_buf_sync sync;
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ;
if (ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync) == -1) {
wp_log(WP_ERROR, "Failed to start sync guard on dmabuf: %s",
strerror(errno));
}
memcpy(destination, mapptr, size);
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ;
if (ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync) == -1) {
wp_log(WP_ERROR, "Failed to end sync guard on dmabuf: %s",
strerror(errno));
}
return 0;
}
bool is_dmabuf(int fd)
{
// Prepare an invalid request, with a dma-buf specific IOCTL
......@@ -163,6 +161,7 @@ bool is_dmabuf(int fd)
return false;
}
}
int get_unique_dmabuf_handle(
struct render_data *rd, int fd, struct gbm_bo **temporary_bo)
{
......@@ -183,21 +182,53 @@ int get_unique_dmabuf_handle(
return handle;
}
struct gbm_bo *make_dmabuf(
struct render_data *rd, const char *data, size_t size)
struct gbm_bo *make_dmabuf(struct render_data *rd, const char *data,
size_t size, struct dmabuf_slice_data *info)
{
uint32_t width = 512;
uint32_t height = (uint32_t)(size + 4 * width - 1) / (4 * width);
uint32_t format = GBM_FORMAT_XRGB8888;
/* Set modifiers to linear, since incoming buffers also will be, thanks
* to the modifier restrictions set in handlers.c */
struct gbm_bo *bo = gbm_bo_create(rd->dev, width, height, format,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
struct gbm_bo *bo;
if (!info || info->num_planes == 0) {
uint32_t width = 512;
uint32_t height =
(uint32_t)(size + 4 * width - 1) / (4 * width);
uint32_t format = GBM_FORMAT_XRGB8888;
/* Set modifiers to linear, the most likely/portable format */
bo = gbm_bo_create(rd->dev, width, height, format,
GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING);
} else {
uint64_t modifiers[2] = {info->modifier, GBM_BO_USE_RENDERING};
// assuming the format is a very standard one which can be
// created by gbm_bo;
int which = 0;
for (int i = 0; i < (int)info->num_planes; i++) {
if (info->using_planes[i]) {
which = i;
}
}
/* Whether just size and modifiers suffice to replicate
* a surface is driver dependent, and requires actual testing
* with the hardware.
*
* i915 DRM ioctls cover size, swizzling, tiling state, only.
* amdgpu, size + allocation domain/caching/align flags
* etnaviv, size + caching flags
* tegra, vc4: size + tiling + flags
* radeon: size + tiling + flags, including pitch
*
* Note that gbm doesn't have a specific api for creating
* buffers with minimal information, or even just getting
* the size of the buffer contents.
*/
int eff_height = size / info->strides[which] + 1;
bo = gbm_bo_create_with_modifiers(rd->dev, info->width,
eff_height, info->format, modifiers, 2);
}
if (!bo) {
wp_log(WP_ERROR, "Failed to make dmabuf: %s", strerror(errno));
return NULL;
}
void *handle = NULL;
// unfortunately, there is no easy way to estimate the writeable region
void *dst = map_dmabuf(bo, true, &handle);
if (!dst) {
gbm_bo_destroy(bo);
......@@ -248,3 +279,43 @@ int unmap_dmabuf(struct gbm_bo *bo, void *map_handle)
gbm_bo_unmap(bo, map_handle);
return 0;
}
// TODO: support DRM formats, like DRM_FORMAT_RGB888_A8 and
// DRM_FORMAT_ARGB16161616F, defined in drm_fourcc.h.
struct multiplanar_info {
uint32_t format;
struct {
int subsample_w;
int subsample_h;
int cpp;
} planes[3];
};
static const struct multiplanar_info plane_table[] = {
{GBM_FORMAT_NV12, {{1, 1, 1}, {2, 2, 2}}},
{GBM_FORMAT_NV21, {{1, 1, 1}, {2, 2, 2}}},
{GBM_FORMAT_NV16, {{1, 1, 1}, {2, 1, 2}}},
{GBM_FORMAT_NV61, {{1, 1, 1}, {2, 1, 2}}},
{GBM_FORMAT_YUV410, {{1, 1, 1}, {4, 4, 1}, {4, 4, 1}}},
{GBM_FORMAT_YVU410, {{1, 1, 1}, {4, 4, 1}, {4, 4, 1}}},
{GBM_FORMAT_YUV411, {{1, 1, 1}, {4, 1, 1}, {4, 1, 1}}},
{GBM_FORMAT_YVU411, {{1, 1, 1}, {4, 1, 1}, {4, 1, 1}}},
{GBM_FORMAT_YUV420, {{1, 1, 1}, {2, 2, 1}, {2, 2, 1}}},
{GBM_FORMAT_YVU420, {{1, 1, 1}, {2, 2, 1}, {2, 2, 1}}},
{GBM_FORMAT_YUV422, {{1, 1, 1}, {2, 1, 1}, {2, 1, 1}}},
{GBM_FORMAT_YVU422, {{1, 1, 1}, {2, 1, 1}, {2, 1, 1}}},
{GBM_FORMAT_YUV444, {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}},
{GBM_FORMAT_YVU444, {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}}, {0}};
uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane)
{
for (int i = 0; plane_table[i].format; i++) {
if (plane_table[i].format == format) {
int cpp = plane_table[i].planes[plane].cpp;
const uint32_t by_cpp[] = {0, GBM_FORMAT_R8,
GBM_FORMAT_GR88, GBM_FORMAT_RGB888,
GBM_BO_FORMAT_ARGB8888};
return by_cpp[cpp];
}
}
return format;
}
......@@ -757,7 +757,7 @@ static void event_wl_keyboard_keymap(void *data,
{
struct context *context = get_context(data, wl_keyboard);
struct shadow_fd *sfd = translate_fd(context->map, fd);
struct shadow_fd *sfd = translate_fd(context->map, fd, NULL);
if (sfd->type != FDC_FILE || sfd->file_size != size) {
wp_log(WP_ERROR,
"keymap candidate RID=%d was not file-like (type=%d), and with size=%ld did not match %d",
......@@ -777,7 +777,7 @@ static void request_wl_shm_create_pool(struct wl_client *client,
struct context *context = get_context(client, resource);
struct wp_shm_pool *the_shm_pool = (struct wp_shm_pool *)listset_get(
&context->mt->objects, id);
struct shadow_fd *sfd = translate_fd(context->map, fd);
struct shadow_fd *sfd = translate_fd(context->map, fd, NULL);
/* It may be valid for the file descriptor size to be larger than the
* immediately advertised size, since the call to wl_shm.create_pool
* may be followed by wl_shm_pool.resize, which then increases the size
......@@ -1018,7 +1018,18 @@ static void request_wl_drm_create_prime_buffer(struct wl_client *client,
struct context *context = get_context(client, resource);
struct wp_buffer *buf = (struct wp_buffer *)listset_get(
&context->mt->objects, id);
struct shadow_fd *sfd = translate_fd(context->map, name);
struct dmabuf_slice_data info;
info.width = (uint32_t)width;
info.height = (uint32_t)height;
info.offsets[0] = (uint32_t)offset0;
info.offsets[1] = (uint32_t)offset1;
info.offsets[2] = (uint32_t)offset2;
info.strides[0] = (uint32_t)stride0;
info.strides[1] = (uint32_t)stride1;
info.strides[2] = (uint32_t)stride2;
info.modifier = 0;
info.format = format;
struct shadow_fd *sfd = translate_fd(context->map, name, &info);
if (sfd->type != FDC_DMABUF) {
wp_log(WP_ERROR,
"keymap candidate RID=%d was not a dmabuf (type=%d)",
......@@ -1035,10 +1046,6 @@ static void request_wl_drm_create_prime_buffer(struct wl_client *client,
// handling multiple offsets (?)
buf->dmabuf_offsets[0] = (uint32_t)offset0;
buf->dmabuf_strides[0] = (uint32_t)stride0;
(void)offset1;
(void)offset2;
(void)stride1;
(void)stride2;
}
static void event_zwp_linux_dmabuf_v1_modifier(void *data,
......@@ -1047,12 +1054,9 @@ static void event_zwp_linux_dmabuf_v1_modifier(void *data,
{
struct context *context = get_context(data, zwp_linux_dmabuf_v1);
(void)format;
// Filter out formats with nonstandard memory layouts; they may not
// be portable between different GPU generations.
// (TODO: better configuration and heuristics for this)
if (modifier_hi || modifier_lo) {
context->drop_this_msg = true;
}
uint64_t modifier = modifier_hi * 0x100000000uL * modifier_lo;
(void)modifier;
(void)context;
}
static void event_zwp_linux_buffer_params_v1_created(void *data,
struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1,
......@@ -1224,9 +1228,34 @@ static void request_zwp_linux_buffer_params_v1_create(struct wl_client *client,
if (!context->on_display_side) {
reintroduce_add_msgs(context, params);
}
struct dmabuf_slice_data info = {
.width = width,
.height = height,
.format = format,
.num_planes = params->nplanes,
.strides = {params->add[0].stride,
params->add[1].stride,
params->add[2].stride,
params->add[3].stride},
.offsets = {params->add[0].offset,
params->add[1].offset,
params->add[2].offset,
params->add[3].offset},
};
for (int i = 0; i < params->nplanes; i++) {
struct shadow_fd *sfd =
translate_fd(context->map, params->add[i].fd);
memset(info.using_planes, 0, sizeof(info.using_planes));
for (int k = 0; k < params->nplanes; k++) {
if (params->add[k].fd == params->add[i].fd) {
info.using_planes[k] = 1;
info.modifier = params->add[k].modifier;
}
}
/* replace the format with something the driver can probably
* handle */
info.format = dmabuf_get_simple_format_for_plane(format, i);
struct shadow_fd *sfd = translate_fd(
context->map, params->add[i].fd, &info);
if (sfd->type != FDC_DMABUF) {
wp_log(WP_ERROR,
"fd #%d for linux-dmabuf request wasn't a dmabuf",
......@@ -1251,49 +1280,16 @@ static void request_zwp_linux_buffer_params_v1_create_immed(
uint32_t format, uint32_t flags)
{
struct context *context = get_context(client, resource);
struct wp_linux_dmabuf_params *params =
(struct wp_linux_dmabuf_params *)context->obj;
struct wp_buffer *buf = (struct wp_buffer *)listset_get(
&context->mt->objects, buffer_id);
buf->type = BUF_DMA;
buf->dmabuf_nplanes = params->nplanes;
deduplicate_dmabuf_fds(context, params);
if (!context->on_display_side) {
// Do this before we wipe any of the fds
reintroduce_add_msgs(context, params);
}
for (int i = 0; i < params->nplanes; i++) {
// TODO: pass in stride info and modifier hints
struct shadow_fd *sfd =
translate_fd(context->map, params->add[i].fd);
if (sfd->type != FDC_DMABUF) {
wp_log(WP_ERROR,
"fd #%d for linux-dmabuf request wasn't a dmabuf",
i);
continue;
}
/* Doubly increment the refcount: once for the decrement after
* transferring, and once due to the pointer ownership here */
if (sfd->has_owner) {
/* increment for each extra time this fd will be
* sent */
shadow_incref_transfer(sfd);
}
buf->dmabuf_buffers[i] = shadow_incref_protocol(sfd);
buf->dmabuf_offsets[i] = params->add[i].offset;
buf->dmabuf_strides[i] = params->add[i].stride;
buf->dmabuf_modifiers[i] = params->add[i].modifier;
}
buf->dmabuf_flags = flags;
buf->dmabuf_width = width;
buf->dmabuf_height = height;
buf->dmabuf_format = format;
/* Remove fds from params, so they aren't closed when the param object
* is destroyed */
for (int i = 0; i < MAX_DMABUF_PLANES; i++) {
params->add[i].fd = -1;
}
params->nplanes = 0;
// There isn't really that much unnecessary copying. Note that
// 'create' may modify messages
request_zwp_linux_buffer_params_v1_create(
(void *)context, NULL, width, height, format, flags);
event_zwp_linux_buffer_params_v1_created(
(void *)context, NULL, (void *)buf);
return;
}
static const struct wl_display_listener wl_display_event_handler = {
......
......@@ -980,7 +980,8 @@ fdcat_t get_fd_type(int fd, size_t *size)
}
}
struct shadow_fd *translate_fd(struct fd_translation_map *map, int fd)
struct shadow_fd *translate_fd(struct fd_translation_map *map, int fd,
struct dmabuf_slice_data *info)
{
struct shadow_fd *cur = map->list;
while (cur) {
......@@ -1043,10 +1044,16 @@ struct shadow_fd *translate_fd(struct fd_translation_map *map, int fd)
init_render_data(&map->rdata);
shadow->dmabuf_bo = import_dmabuf(&map->rdata, shadow->fd_local,
&shadow->dmabuf_size);
&shadow->dmabuf_size, info);
if (!shadow->dmabuf_bo) {
return shadow;
}
if (info) {
memcpy(&shadow->dmabuf_info, info,
sizeof(struct dmabuf_slice_data));
} else {
// already zero initialized (no information).
}
// to be created on first transfer
shadow->dmabuf_mem_mirror = NULL;
shadow->dmabuf_diff_buffer = NULL;
......@@ -1058,7 +1065,7 @@ void translate_fds(struct fd_translation_map *map, int nfds, const int fds[],
int ids[])
{
for (int i = 0; i < nfds; i++) {
ids[i] = translate_fd(map, fds[i])->remote_id;
ids[i] = translate_fd(map, fds[i], NULL)->remote_id;
}
}
struct shadow_fd *get_shadow_for_local_fd(
......@@ -1279,7 +1286,14 @@ void collect_updates(struct fd_translation_map *map, int *ntransfers,
if (!cur->dmabuf_mem_mirror) {
cur->dmabuf_mem_mirror =
calloc(1, cur->dmabuf_size);
// 8 extra bytes for worst case diff expansion
// 8 extra bytes for diff messages, or
// alternatively for type header info
size_t diffb_size =
(size_t)max(sizeof(struct dmabuf_slice_data),
8) +
(size_t)align((int)cur->dmabuf_size,
8);
cur->dmabuf_diff_buffer = calloc(diffb_size, 1);
first = true;
}
void *handle = NULL;
......@@ -1292,14 +1306,25 @@ void collect_updates(struct fd_translation_map *map, int *ntransfers,
continue;
}
if (first) {
// Write diff with a header, and build mirror,
// only touching data once
memcpy(cur->dmabuf_mem_mirror, data,
cur->dmabuf_size);
memcpy(cur->dmabuf_diff_buffer,
&cur->dmabuf_info,
sizeof(struct dmabuf_slice_data));
memcpy(cur->dmabuf_diff_buffer +
sizeof(struct dmabuf_slice_data),
cur->dmabuf_mem_mirror,
cur->dmabuf_size);
// new transfer, we send file contents verbatim
wp_log(WP_DEBUG, "Sending initial dmabuf");
int nt = (*ntransfers)++;
transfers[nt].data = cur->dmabuf_mem_mirror;
transfers[nt].size = cur->dmabuf_size;
transfers[nt].data = cur->dmabuf_diff_buffer;
transfers[nt].size =
cur->dmabuf_size +
sizeof(struct dmabuf_slice_data);
transfers[nt].type = cur->type;
transfers[nt].obj_id = cur->remote_id;
transfers[nt].special = 0;
......@@ -1307,16 +1332,6 @@ void collect_updates(struct fd_translation_map *map, int *ntransfers,
bool delta = memcmp(cur->dmabuf_mem_mirror,
data, cur->dmabuf_size);
if (delta) {
if (!cur->dmabuf_diff_buffer) {
/* Create diff buffer by need
* for remote files
*/
cur->dmabuf_diff_buffer = calloc(
cur->dmabuf_size +
8,
1);
}
// TODO: damage region support!
size_t diffsize;
wp_log(WP_DEBUG,
......@@ -1712,12 +1727,21 @@ static void apply_update(
shadow->fd_local = -1;
return;
}
struct dmabuf_slice_data *info =
(struct dmabuf_slice_data *)transf->data;
char *contents =
transf->data + sizeof(struct dmabuf_slice_data);
size_t contents_size =
transf->size - sizeof(struct dmabuf_slice_data);
shadow->dmabuf_bo = make_dmabuf(
&map->rdata, transf->data, transf->size);
&map->rdata, contents, contents_size, info);
if (!shadow->dmabuf_bo) {
shadow->fd_local = -1;
return;
}
memcpy(&shadow->dmabuf_info, info,
sizeof(struct dmabuf_slice_data));
shadow->fd_local = export_dmabuf(shadow->dmabuf_bo);
} else {
wp_log(WP_ERROR, "Creating unknown file type updates");
......
......@@ -104,6 +104,22 @@ struct pipe_buffer {
ssize_t used;
};
struct dmabuf_slice_data {
/* This information partially duplicates that of a gbm_bo. However, for
* instance with weston, it is possible for the compositor to handle
* multibuffer multiplanar images, even though a driver may only support
* multiplanar images derived from a single underlying dmabuf. */
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t num_planes;
// to which planes is the matching dmabuf assigned?
uint8_t using_planes[4];
uint32_t strides[4];
uint32_t offsets[4];
uint64_t modifier;
};
struct shadow_fd {
struct shadow_fd *next; // singly-linked list
fdcat_t type;
......@@ -150,6 +166,7 @@ struct shadow_fd {
struct gbm_bo *dmabuf_bo;
char *dmabuf_mem_mirror; // malloc'd
char *dmabuf_diff_buffer; // malloc'd
struct dmabuf_slice_data dmabuf_info;
};
struct transfer {
......@@ -169,8 +186,12 @@ void cleanup_translation_map(struct fd_translation_map *map);
* the object is an FDC_FILE. */
fdcat_t get_fd_type(int fd, size_t *size);
/** Given a list of local file descriptors, produce matching global ids, and
* register them into the translation map if not already done. */
struct shadow_fd *translate_fd(struct fd_translation_map *map, int fd);
* register them into the translation map if not already done. The specific
* translate_fd function can also be provided with optional extra information.
*/
struct dmabuf_slice_data;
struct shadow_fd *translate_fd(struct fd_translation_map *map, int fd,
struct dmabuf_slice_data *info);
void translate_fds(struct fd_translation_map *map, int nfds, const int fds[],
int ids[]);
/** Produce a list of file updates to transfer. All pointers will be to existing
......@@ -358,13 +379,16 @@ extern const struct msg_handler handlers[];
extern const struct wl_interface *the_display_interface;
// dmabuf.c
/* Additional information to help serialize a dmabuf */
int init_render_data(struct render_data *);
void cleanup_render_data(struct render_data *);
bool is_dmabuf(int fd);
struct gbm_bo *make_dmabuf(
struct render_data *rd, const char *data, size_t size);
struct gbm_bo *make_dmabuf(struct render_data *rd, const char *data,
size_t size, struct dmabuf_slice_data *info);
int export_dmabuf(struct gbm_bo *bo);
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size);
struct gbm_bo *import_dmabuf(struct render_data *rd, int fd, size_t *size,
struct dmabuf_slice_data *info);
void destroy_dmabuf(struct gbm_bo *bo);
void *map_dmabuf(struct gbm_bo *bo, bool write, void **map_handle);
int unmap_dmabuf(struct gbm_bo *bo, void *map_handle);
......@@ -373,5 +397,6 @@ int unmap_dmabuf(struct gbm_bo *bo, void *map_handle);
* then free the temporary buffer objects in a batch */
int get_unique_dmabuf_handle(
struct render_data *rd, int fd, struct gbm_bo **temporary_bo);
uint32_t dmabuf_get_simple_format_for_plane(uint32_t format, int plane);
#endif // WAYPIPE_UTIL_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment