From 5aafd6296f7aa29d619934609e508016510fc06f Mon Sep 17 00:00:00 2001 From: Changyeon Lee Date: Tue, 27 Feb 2024 13:06:36 +0900 Subject: [PATCH] add ds_linux_dmabuf_v1 implementation of zwp_linux_dmabuf_v1 protocol Change-Id: Ide68bbe27ed7365cad3caf9e58d1c2e8a5888b85 --- include/libds/linux_dmabuf_v1.h | 41 +++ src/client_buffer/linux_dmabuf_v1.c | 573 ++++++++++++++++++++++++++++++++++++ src/meson.build | 2 + 3 files changed, 616 insertions(+) create mode 100644 include/libds/linux_dmabuf_v1.h create mode 100644 src/client_buffer/linux_dmabuf_v1.c diff --git a/include/libds/linux_dmabuf_v1.h b/include/libds/linux_dmabuf_v1.h new file mode 100644 index 0000000..260974c --- /dev/null +++ b/include/libds/linux_dmabuf_v1.h @@ -0,0 +1,41 @@ +#ifndef LIBDS_LINUX_DMABUF_V1_H +#define LIBDS_LINUX_DMABUF_V1_H + +#include +#include + +#define LINUX_DMABUF_MAX_PLANES 4 + +struct ds_linux_dmabuf_v1; + +struct ds_linux_dmabuf_v1_buffer; + +struct ds_linux_dmabuf_v1_attributes { + int32_t width; + int32_t height; + uint32_t format; + uint32_t flags; + int num_planes; + int fd[LINUX_DMABUF_MAX_PLANES]; + uint32_t offset[LINUX_DMABUF_MAX_PLANES]; + uint32_t stride[LINUX_DMABUF_MAX_PLANES]; + uint64_t modifier[LINUX_DMABUF_MAX_PLANES]; +}; + +struct ds_linux_dmabuf_v1_format { + uint32_t format; + uint64_t *modifiers; + int num_modifiers; +}; + +struct ds_linux_dmabuf_v1 * +ds_linux_dmabuf_v1_create(struct wl_display *display, + const struct ds_linux_dmabuf_v1_format *supported_formats, int num_formats); + +struct ds_linux_dmabuf_v1_buffer * +ds_linux_dmabuf_v1_buffer_from_buffer(struct ds_buffer *ds_buffer); + +const struct ds_linux_dmabuf_v1_attributes * +ds_linux_dmabuf_v1_buffer_get_attributes(struct ds_linux_dmabuf_v1_buffer *dmabuf_buffer); + +#endif diff --git a/src/client_buffer/linux_dmabuf_v1.c b/src/client_buffer/linux_dmabuf_v1.c new file mode 100644 index 0000000..88769ab --- /dev/null +++ b/src/client_buffer/linux_dmabuf_v1.c @@ -0,0 +1,573 @@ +#include +#include +#include + +#include "libds/log.h" +#include "libds/util/defs.h" +#include "libds/interfaces/buffer.h" +#include "libds/linux_dmabuf_v1.h" + +#include "pixel_format.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" + +#define LINUX_DMABUF_V1_VERSION 3 + +struct ds_linux_dmabuf_v1 +{ + struct wl_global *global; + + struct wl_listener display_destroy; + + struct ds_linux_dmabuf_v1_format *supported_formats; + int num_formats; +}; + +struct ds_linux_dmabuf_v1_buffer { + struct ds_buffer base; + struct wl_resource *buffer_resource; + struct wl_resource *params_resource; + struct ds_linux_dmabuf_v1_attributes attributes; + + struct { + struct wl_listener buffer_release; + } listener; +}; + +static const struct wl_buffer_interface linux_dmabuf_v1_buffer_impl; + +static bool +linux_dmabuf_v1_buffer_is_instance(struct wl_resource *resource) +{ + return wl_resource_instance_of(resource, &wl_buffer_interface, + &linux_dmabuf_v1_buffer_impl); +} + +static struct ds_linux_dmabuf_v1_buffer * +linux_dmabuf_v1_buffer_from_resource(struct wl_resource *resource) +{ + DS_ASSERT(linux_dmabuf_v1_buffer_is_instance(resource)); + return wl_resource_get_user_data(resource); +} + +static struct ds_buffer * +linux_dmabuf_from_resource(struct wl_resource *resource) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = linux_dmabuf_v1_buffer_from_resource(resource); + return &buffer->base; +} + +static const struct ds_buffer_resource_interface +linux_dmabuf_resource_iface = { + .name = "ds_linux_dmabuf_v1", + .is_instance = linux_dmabuf_v1_buffer_is_instance, + .from_resource = linux_dmabuf_from_resource, +}; + +static void +linux_dmabuf_v1_buffer_destroy(struct ds_linux_dmabuf_v1_buffer *buffer) +{ + int i; + + for (i = 0; i < buffer->attributes.num_planes; i++) { + if (buffer->attributes.fd[i] == -1) continue; + + close(buffer->attributes.fd[i]); + } + + free(buffer); +} + +static void +linux_dmabuf_v1_buffer_handle_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface linux_dmabuf_v1_buffer_impl = { + linux_dmabuf_v1_buffer_handle_destroy, +}; + +static void +linux_dmabuf_v1_buffer_iface_destroy(struct ds_buffer *ds_buffer) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_container_of(ds_buffer, buffer, base); + linux_dmabuf_v1_buffer_destroy(buffer); +} + +static struct wl_resource * +linux_dmabuf_v1_buffer_iface_get_resource(struct ds_buffer *ds_buffer) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_container_of(ds_buffer, buffer, base); + return buffer->buffer_resource; +} + +static const struct ds_buffer_interface linux_dmabuf_v1_buffer_iface = { + .destroy = linux_dmabuf_v1_buffer_iface_destroy, + .get_resource = linux_dmabuf_v1_buffer_iface_get_resource, +}; + +static void linux_dmabuf_v1_handle_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +linux_dmabuf_params_handle_resource_destroy(struct wl_resource *resource) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_resource_get_user_data(resource); + if (!buffer) + return; + + linux_dmabuf_v1_buffer_destroy(buffer); +} + +static void +linux_dmabuf_params_handle_destroy(struct wl_client *client, + struct wl_resource *params_resource) +{ + wl_resource_destroy(params_resource); +} + +static void +linux_dmabuf_params_handle_add(struct wl_client *client, + struct wl_resource *params_resource, + int32_t fd, + uint32_t plane_idx, + uint32_t offset, + uint32_t stride, + uint32_t modifier_hi, + uint32_t modifier_lo) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_resource_get_user_data(params_resource); + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + close(fd); + return; + } + + if (plane_idx >= LINUX_DMABUF_MAX_PLANES) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "plane index %u is too high", plane_idx); + close(fd); + return; + } + + if (buffer->attributes.fd[plane_idx] != -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "a dmabuf has already been added for plane %u", + plane_idx); + close(fd); + return; + } + + buffer->attributes.fd[plane_idx] = fd; + buffer->attributes.offset[plane_idx] = offset; + buffer->attributes.stride[plane_idx] = stride; + + if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) + buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID; + else + buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | modifier_lo; + + buffer->attributes.num_planes++; +} + +static void +linux_dmabuf_v1_buffer_handle_resource_destroy(struct wl_resource *resource) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_resource_get_user_data(resource); + assert(buffer->buffer_resource == resource); + assert(!buffer->params_resource); + + buffer->buffer_resource = NULL; + ds_buffer_drop(&buffer->base); +} + +static void +linux_dmabuf_v1_buffer_handle_release(struct wl_listener *listener, void *data) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + buffer = wl_container_of(listener, buffer, listener.buffer_release); + if (buffer->buffer_resource) + wl_buffer_send_release(buffer->buffer_resource); +} + +static void +params_create_common(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + int i; + + buffer = wl_resource_get_user_data(params_resource); + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + + assert(buffer->params_resource == params_resource); + assert(!buffer->buffer_resource); + + /* Switch the linux_dmabuf_v1_buffer object from params resource to + * eventually wl_buffer resource. + */ + wl_resource_set_user_data(buffer->params_resource, NULL); + buffer->params_resource = NULL; + + if (!buffer->attributes.num_planes) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added to the params"); + goto err_out; + } + + /* Check for holes in the dmabufs set (e.g. [0, 1, 3]) */ + for (i = 0; i < buffer->attributes.num_planes; i++) { + if (buffer->attributes.fd[i] == -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added for plane %i", i); + goto err_out; + } + } + + buffer->attributes.width = width; + buffer->attributes.height = height; + buffer->attributes.format = format; + buffer->attributes.flags = flags; + + if (width < 1 || height < 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, + "invalid width %d or height %d", width, height); + goto err_out; + } + + for (i = 0; i < buffer->attributes.num_planes; i++) { + off_t size; + + if ((uint64_t) buffer->attributes.offset[i] + buffer->attributes.stride[i] > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %i", i); + goto err_out; + } + + if (i == 0 && + (uint64_t) buffer->attributes.offset[i] + + (uint64_t) buffer->attributes.stride[i] * height > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %i", i); + goto err_out; + } + + /* Don't report an error as it might be caused + * by the kernel not supporting seeking on dmabuf */ + size = lseek(buffer->attributes.fd[i], 0, SEEK_END); + if (size == -1) + continue; + + if (buffer->attributes.offset[i] >= size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid offset %i for plane %i", + buffer->attributes.offset[i], i); + goto err_out; + } + + if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid stride %i for plane %i", + buffer->attributes.stride[i], i); + goto err_out; + } + + /* Only valid for first plane as other planes might be + * sub-sampled according to fourcc format */ + if (i == 0 && + (uint64_t) buffer->attributes.offset[i] + + (uint64_t) buffer->attributes.stride[i] * height > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid buffer stride or height for plane %i", i); + goto err_out; + } + } + + buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface, 1, buffer_id); + if (!buffer->buffer_resource) { + wl_resource_post_no_memory(params_resource); + goto err_failed; + } + + wl_resource_set_implementation(buffer->buffer_resource, + &linux_dmabuf_v1_buffer_impl, + buffer, linux_dmabuf_v1_buffer_handle_resource_destroy); + + /* send 'created' event when the request is not for an immediate + * import, ie buffer_id is zero */ + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_created(params_resource, + buffer->buffer_resource); + + ds_buffer_init(&buffer->base, &linux_dmabuf_v1_buffer_iface, width, height); + + buffer->listener.buffer_release.notify = + linux_dmabuf_v1_buffer_handle_release; + ds_buffer_add_release_listener(&buffer->base, + &buffer->listener.buffer_release); + + return; + +err_failed: + if (buffer_id == 0) + zwp_linux_buffer_params_v1_send_failed(params_resource); + else + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); + +err_out: + linux_dmabuf_v1_buffer_destroy(buffer); +} + + +static void +linux_dmabuf_params_handle_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, 0, width, height, format, + flags); +} + +static void +linux_dmabuf_params_handle_create_immed(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags) +{ + params_create_common(client, params_resource, buffer_id, width, height, + format, flags); +} + +static const struct zwp_linux_buffer_params_v1_interface linux_dmabuf_params_impl = { + .destroy = linux_dmabuf_params_handle_destroy, + .add = linux_dmabuf_params_handle_add, + .create = linux_dmabuf_params_handle_create, + .create_immed = linux_dmabuf_params_handle_create_immed, +}; + +static void +linux_dmabuf_v1_handle_create_params(struct wl_client *client, + struct wl_resource *linux_dmabuf_resource, + uint32_t id) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + uint32_t version; + int i; + + version = wl_resource_get_version(linux_dmabuf_resource); + + buffer = calloc(1, sizeof *buffer); + if (!buffer) { + wl_client_post_no_memory(client); + return; + } + + buffer->params_resource = wl_resource_create(client, + &zwp_linux_buffer_params_v1_interface, version, id); + if (!buffer->params_resource) { + wl_client_post_no_memory(client); + free(buffer); + return; + } + + for (i = 0; i < LINUX_DMABUF_MAX_PLANES; i++ ) + buffer->attributes.fd[i] = -1; + + wl_resource_set_implementation(buffer->params_resource, + &linux_dmabuf_params_impl, buffer, + linux_dmabuf_params_handle_resource_destroy); +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_v1_impl = { + .destroy = linux_dmabuf_v1_handle_destroy, + .create_params = linux_dmabuf_v1_handle_create_params, +}; + +static void +linux_dmabuf_v1_send_formats(struct ds_linux_dmabuf_v1 *linux_dmabuf, + struct wl_resource *resource) +{ + int i, j; + + for (i = 0; i < linux_dmabuf->num_formats; i++) { + for (j = 0; j < linux_dmabuf->supported_formats[i].num_modifiers; j++) { + if (wl_resource_get_version(resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + if ((linux_dmabuf->supported_formats[i].modifiers[j] == DRM_FORMAT_MOD_INVALID) || + (linux_dmabuf->supported_formats[i].modifiers[j] == DRM_FORMAT_MOD_LINEAR)) + zwp_linux_dmabuf_v1_send_format(resource, linux_dmabuf->supported_formats[i].format); + } else { + zwp_linux_dmabuf_v1_send_modifier(resource, linux_dmabuf->supported_formats[i].format, + linux_dmabuf->supported_formats[i].modifiers[j] >> 32, + linux_dmabuf->supported_formats[i].modifiers[j] & 0xFFFFFFFF); + } + } + } +} + +static void linux_dmabuf_v1_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + struct ds_linux_dmabuf_v1 *linux_dmabuf = data; + + resource = wl_resource_create(client, + &zwp_linux_dmabuf_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &linux_dmabuf_v1_impl, NULL, NULL); + + linux_dmabuf_v1_send_formats(linux_dmabuf, resource); +} + +static void +linux_dmabuf_v1_handle_display_destroy(struct wl_listener *listener, void *data) +{ + struct ds_linux_dmabuf_v1 *linux_dmabuf; + int i; + + linux_dmabuf = wl_container_of(listener, linux_dmabuf, display_destroy); + wl_global_destroy(linux_dmabuf->global); + + for (i = 0; i < linux_dmabuf->num_formats; i++) { + if (linux_dmabuf->supported_formats[i].modifiers) + free(linux_dmabuf->supported_formats[i].modifiers); + } + + free(linux_dmabuf->supported_formats); + free(linux_dmabuf); +} + +WL_EXPORT struct ds_linux_dmabuf_v1 * +ds_linux_dmabuf_v1_create(struct wl_display *display, + const struct ds_linux_dmabuf_v1_format *supported_formats, int num_formats) +{ + struct ds_linux_dmabuf_v1 *linux_dmabuf; + int i, j; + + DS_RETURN_VAL_IF_FAIL(display, NULL); + + linux_dmabuf = calloc(1, sizeof *linux_dmabuf); + if (!linux_dmabuf) + return NULL; + + linux_dmabuf->supported_formats = calloc(num_formats, sizeof *supported_formats); + if (!linux_dmabuf->supported_formats) { + goto failed; + } + + linux_dmabuf->num_formats = num_formats; + + for (i = 0; i < num_formats; i++) { + linux_dmabuf->supported_formats[i].format = supported_formats[i].format; + + linux_dmabuf->supported_formats[i].modifiers = calloc(linux_dmabuf->supported_formats[i].num_modifiers, + sizeof *linux_dmabuf->supported_formats[i].modifiers); + if (!linux_dmabuf->supported_formats[i].modifiers) + goto failed; + + for (j = 0; j < linux_dmabuf->supported_formats[i].num_modifiers; j++) + linux_dmabuf->supported_formats[i].modifiers[j] = supported_formats[i].modifiers[j]; + + linux_dmabuf->supported_formats[i].num_modifiers = supported_formats[i].num_modifiers; + } + + linux_dmabuf->global = wl_global_create(display, + &zwp_linux_dmabuf_v1_interface, LINUX_DMABUF_V1_VERSION, + linux_dmabuf, linux_dmabuf_v1_bind); + if (!linux_dmabuf->global) + goto failed; + + linux_dmabuf->display_destroy.notify = linux_dmabuf_v1_handle_display_destroy; + wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy); + + ds_buffer_register_resource_interface(&linux_dmabuf_resource_iface); + + return linux_dmabuf; + +failed: + for (i = 0; i < linux_dmabuf->num_formats; i++) { + if (linux_dmabuf->supported_formats[i].modifiers) + free(linux_dmabuf->supported_formats[i].modifiers); + } + + free(linux_dmabuf->supported_formats); + free(linux_dmabuf); + + return NULL; +} + +WL_EXPORT struct ds_linux_dmabuf_v1_buffer * +ds_linux_dmabuf_v1_buffer_from_buffer(struct ds_buffer *ds_buffer) +{ + struct ds_linux_dmabuf_v1_buffer *buffer; + + DS_RETURN_VAL_IF_FAIL(ds_buffer, NULL); + + if (ds_buffer->iface != &linux_dmabuf_v1_buffer_iface) + return NULL; + + return wl_container_of(ds_buffer, buffer, base); +} + +WL_EXPORT const struct ds_linux_dmabuf_v1_attributes * +ds_linux_dmabuf_v1_buffer_get_attributes(struct ds_linux_dmabuf_v1_buffer *dmabuf_buffer) +{ + DS_RETURN_VAL_IF_FAIL(dmabuf_buffer, NULL); + + return &dmabuf_buffer->attributes; +} diff --git a/src/meson.build b/src/meson.build index d9a9410..a96961e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,6 +16,7 @@ libds_files = [ 'compositor/subsurface.c', 'client_buffer/shm_client_buffer.c', 'client_buffer/single_pixel_buffer_v1.c', + 'client_buffer/linux_dmabuf_v1.c', 'xdg_shell/xdg_shell.c', 'xdg_shell/xdg_surface.c', 'xdg_shell/xdg_toplevel.c', @@ -36,6 +37,7 @@ libds_files = [ protocols = { 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', + 'linux-dmabuf-v1': wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', } protocols_code = {} -- 2.7.4