diff --git a/include/wlr/types/wlr_linux_dmabuf_v1.h b/include/wlr/types/wlr_linux_dmabuf_v1.h index b4eb8675..bbbb2772 100644 --- a/include/wlr/types/wlr_linux_dmabuf_v1.h +++ b/include/wlr/types/wlr_linux_dmabuf_v1.h @@ -10,6 +10,7 @@ #define WLR_TYPES_WLR_LINUX_DMABUF_H #include +#include #include #include #include @@ -38,6 +39,18 @@ bool wlr_dmabuf_v1_resource_is_buffer(struct wl_resource *buffer_resource); struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_buffer_resource( struct wl_resource *buffer_resource); +struct wlr_linux_dmabuf_feedback_v1 { + dev_t main_device; + size_t tranches_len; + const struct wlr_linux_dmabuf_feedback_v1_tranche *tranches; +}; + +struct wlr_linux_dmabuf_feedback_v1_tranche { + dev_t target_device; + uint32_t flags; // bitfield of enum zwp_linux_dmabuf_feedback_v1_tranche_flags + const struct wlr_drm_format_set *formats; +}; + /* the protocol interface */ struct wlr_linux_dmabuf_v1 { struct wl_global *global; @@ -49,6 +62,8 @@ struct wlr_linux_dmabuf_v1 { // private state + struct wlr_linux_dmabuf_feedback_v1_compiled *default_feedback; + struct wl_listener display_destroy; struct wl_listener renderer_destroy; }; diff --git a/protocol/meson.build b/protocol/meson.build index 8d8b2502..3e34f788 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.23', + version: '>=1.24', fallback: ['wayland-protocols', 'wayland_protocols'], default_options: ['tests=false'], ) diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c index b91fb59d..325b5362 100644 --- a/types/wlr_linux_dmabuf_v1.c +++ b/types/wlr_linux_dmabuf_v1.c @@ -2,17 +2,17 @@ #include #include #include +#include #include -#include -#include #include #include #include #include "linux-dmabuf-unstable-v1-protocol.h" #include "render/drm_format_set.h" #include "util/signal.h" +#include "util/shm.h" -#define LINUX_DMABUF_VERSION 3 +#define LINUX_DMABUF_VERSION 4 struct wlr_linux_buffer_params_v1 { struct wl_resource *resource; @@ -21,6 +21,32 @@ struct wlr_linux_buffer_params_v1 { bool has_modifier; }; +struct wlr_linux_dmabuf_feedback_v1_compiled_tranche { + dev_t target_device; + uint32_t flags; // bitfield of enum zwp_linux_dmabuf_feedback_v1_tranche_flags + struct wl_array indices; // uint16_t +}; + +struct wlr_linux_dmabuf_feedback_v1_compiled { + dev_t main_device; + int table_fd; + size_t table_size; + + size_t tranches_len; + struct wlr_linux_dmabuf_feedback_v1_compiled_tranche tranches[]; +}; + +struct wlr_linux_dmabuf_feedback_v1_table_entry { + uint32_t format; + uint32_t pad; // unused + uint64_t modifier; +}; + +// TODO: switch back to static_assert once this fix propagates in stable trees: +// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=255290 +_Static_assert(sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry) == 16, + "Expected wlr_linux_dmabuf_feedback_v1_table_entry to be tightly packed"); + static void buffer_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); @@ -415,6 +441,228 @@ static void linux_dmabuf_create_params(struct wl_client *client, &buffer_params_impl, params, params_handle_resource_destroy); } +static void linux_dmabuf_feedback_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_feedback_v1_interface + linux_dmabuf_feedback_impl = { + .destroy = linux_dmabuf_feedback_destroy, +}; + +static struct wlr_linux_dmabuf_feedback_v1_compiled *feedback_compile( + const struct wlr_linux_dmabuf_feedback_v1 *feedback) { + assert(feedback->tranches_len > 0); + + // Require the last tranche to be the fallback tranche and contain all + // formats/modifiers + const struct wlr_linux_dmabuf_feedback_v1_tranche *fallback_tranche = + &feedback->tranches[feedback->tranches_len - 1]; + + size_t table_len = 0; + for (size_t i = 0; i < fallback_tranche->formats->len; i++) { + const struct wlr_drm_format *fmt = fallback_tranche->formats->formats[i]; + table_len += 1 + fmt->len; + } + assert(table_len > 0); + + size_t table_size = + table_len * sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry); + int rw_fd, ro_fd; + if (!allocate_shm_file_pair(table_size, &rw_fd, &ro_fd)) { + wlr_log(WLR_ERROR, "Failed to allocate shm file for format table"); + return NULL; + } + + struct wlr_linux_dmabuf_feedback_v1_table_entry *table = + mmap(NULL, table_size, PROT_READ | PROT_WRITE, MAP_SHARED, rw_fd, 0); + if (table == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap failed"); + close(rw_fd); + close(ro_fd); + return NULL; + } + + close(rw_fd); + + size_t n = 0; + for (size_t i = 0; i < fallback_tranche->formats->len; i++) { + const struct wlr_drm_format *fmt = fallback_tranche->formats->formats[i]; + + table[n] = (struct wlr_linux_dmabuf_feedback_v1_table_entry){ + .format = fmt->format, + .modifier = DRM_FORMAT_MOD_INVALID, + }; + n++; + + for (size_t k = 0; k < fmt->len; k++) { + table[n] = (struct wlr_linux_dmabuf_feedback_v1_table_entry){ + .format = fmt->format, + .modifier = fmt->modifiers[k], + }; + n++; + } + } + assert(n == table_len); + + munmap(table, table_size); + + struct wlr_linux_dmabuf_feedback_v1_compiled *compiled = calloc(1, + sizeof(struct wlr_linux_dmabuf_feedback_v1_compiled) + + feedback->tranches_len * sizeof(struct wlr_linux_dmabuf_feedback_v1_compiled_tranche)); + if (compiled == NULL) { + close(ro_fd); + return NULL; + } + + compiled->main_device = feedback->main_device; + compiled->tranches_len = feedback->tranches_len; + compiled->table_fd = ro_fd; + compiled->table_size = table_size; + + // Build the indices lists for all but the last (fallback) tranches + for (size_t i = 0; i < feedback->tranches_len - 1; i++) { + assert(false); // TODO: unimplemented + } + + struct wlr_linux_dmabuf_feedback_v1_compiled_tranche *fallback_compiled_tranche = + &compiled->tranches[compiled->tranches_len - 1]; + fallback_compiled_tranche->target_device = fallback_tranche->target_device; + fallback_compiled_tranche->flags = fallback_tranche->flags; + + // Build the indices list for the last (fallback) tranche + wl_array_init(&fallback_compiled_tranche->indices); + if (!wl_array_add(&fallback_compiled_tranche->indices, + table_len * sizeof(uint16_t))) { + wlr_log(WLR_ERROR, "Failed to allocate fallback tranche indices array"); + goto error_compiled; + } + + n = 0; + uint16_t *index_ptr; + wl_array_for_each(index_ptr, &fallback_compiled_tranche->indices) { + *index_ptr = n; + n++; + } + + return compiled; + +error_compiled: + close(compiled->table_fd); + free(compiled); + return NULL; +} + +static void compiled_feedback_destroy( + struct wlr_linux_dmabuf_feedback_v1_compiled *feedback) { + for (size_t i = 0; i < feedback->tranches_len; i++) { + wl_array_release(&feedback->tranches[i].indices); + } + close(feedback->table_fd); + free(feedback); +} + +static bool feedback_tranche_init_with_renderer( + struct wlr_linux_dmabuf_feedback_v1_tranche *tranche, + struct wlr_renderer *renderer) { + memset(tranche, 0, sizeof(*tranche)); + + int drm_fd = wlr_renderer_get_drm_fd(renderer); + if (drm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM FD from renderer"); + return false; + } + + struct stat stat; + if (fstat(drm_fd, &stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return false; + } + tranche->target_device = stat.st_rdev; + + tranche->formats = wlr_renderer_get_dmabuf_texture_formats(renderer); + if (tranche->formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get renderer DMA-BUF texture formats"); + return false; + } + + return true; +} + +static struct wlr_linux_dmabuf_feedback_v1_compiled *compile_default_feedback( + struct wlr_renderer *renderer) { + struct wlr_linux_dmabuf_feedback_v1_tranche tranche = {0}; + if (!feedback_tranche_init_with_renderer(&tranche, renderer)) { + return NULL; + } + + const struct wlr_linux_dmabuf_feedback_v1 feedback = { + .main_device = tranche.target_device, + .tranches = &tranche, + .tranches_len = 1, + }; + + return feedback_compile(&feedback); +} + +static void feedback_tranche_send( + const struct wlr_linux_dmabuf_feedback_v1_compiled_tranche *tranche, + struct wl_resource *resource) { + struct wl_array dev_array = { + .size = sizeof(tranche->target_device), + .data = (void *)&tranche->target_device, + }; + zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(resource, &dev_array); + zwp_linux_dmabuf_feedback_v1_send_tranche_flags(resource, tranche->flags); + zwp_linux_dmabuf_feedback_v1_send_tranche_formats(resource, + (struct wl_array *)&tranche->indices); + zwp_linux_dmabuf_feedback_v1_send_tranche_done(resource); +} + +static void feedback_send(const struct wlr_linux_dmabuf_feedback_v1_compiled *feedback, + struct wl_resource *resource) { + struct wl_array dev_array = { + .size = sizeof(feedback->main_device), + .data = (void *)&feedback->main_device, + }; + zwp_linux_dmabuf_feedback_v1_send_main_device(resource, &dev_array); + + zwp_linux_dmabuf_feedback_v1_send_format_table(resource, + feedback->table_fd, feedback->table_size); + + for (size_t i = 0; i < feedback->tranches_len; i++) { + feedback_tranche_send(&feedback->tranches[i], resource); + } + + zwp_linux_dmabuf_feedback_v1_send_done(resource); +} + +static void linux_dmabuf_get_default_feedback(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + linux_dmabuf_from_resource(resource); + + uint32_t version = wl_resource_get_version(resource); + struct wl_resource *feedback_resource = wl_resource_create(client, + &zwp_linux_dmabuf_feedback_v1_interface, version, id); + if (feedback_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(feedback_resource, &linux_dmabuf_feedback_impl, + NULL, NULL); + + feedback_send(linux_dmabuf->default_feedback, feedback_resource); +} + +static void linux_dmabuf_get_surface_feedback(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + // TODO: implement per-surface feedback + linux_dmabuf_get_default_feedback(client, resource, id); +} + static void linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); @@ -423,6 +671,8 @@ static void linux_dmabuf_destroy(struct wl_client *client, static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = { .destroy = linux_dmabuf_destroy, .create_params = linux_dmabuf_create_params, + .get_default_feedback = linux_dmabuf_get_default_feedback, + .get_surface_feedback = linux_dmabuf_get_surface_feedback, }; static void linux_dmabuf_send_modifiers(struct wl_resource *resource, @@ -477,12 +727,17 @@ static void linux_dmabuf_bind(struct wl_client *client, void *data, } wl_resource_set_implementation(resource, &linux_dmabuf_impl, linux_dmabuf, NULL); - linux_dmabuf_send_formats(linux_dmabuf, resource); + + if (version < ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) { + linux_dmabuf_send_formats(linux_dmabuf, resource); + } } static void linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf) { wlr_signal_emit_safe(&linux_dmabuf->events.destroy, linux_dmabuf); + compiled_feedback_destroy(linux_dmabuf->default_feedback); + wl_list_remove(&linux_dmabuf->display_destroy.link); wl_list_remove(&linux_dmabuf->renderer_destroy.link); @@ -523,6 +778,14 @@ struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *displa return NULL; } + linux_dmabuf->default_feedback = compile_default_feedback(renderer); + if (linux_dmabuf->default_feedback == NULL) { + wlr_log(WLR_ERROR, "Failed to init default linux-dmabuf feedback"); + wl_global_destroy(linux_dmabuf->global); + free(linux_dmabuf); + return NULL; + } + linux_dmabuf->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy);