linux-dmabuf-v1: implement v4

Implement a basic version of linux-dmabuf-unstable-v1 version 4.
Only default hints are implemented.

The new wlr_linux_dmabuf_feedback_v1 data structure will allow
compositors to define their own custom hints in the future. This
data structure makes it easy to describe feedback metadata.

It's converted to a "compiled" form suitable for marshalling over
the Wayland socket via feedback_compile.
This commit is contained in:
Simon Ser 2018-11-13 12:35:08 +01:00
parent 77d811a21b
commit 1d8340754b
3 changed files with 283 additions and 5 deletions

View file

@ -10,6 +10,7 @@
#define WLR_TYPES_WLR_LINUX_DMABUF_H
#include <stdint.h>
#include <sys/stat.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/render/dmabuf.h>
@ -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;
};

View file

@ -1,5 +1,5 @@
wayland_protos = dependency('wayland-protocols',
version: '>=1.23',
version: '>=1.24',
fallback: ['wayland-protocols', 'wayland_protocols'],
default_options: ['tests=false'],
)

View file

@ -2,17 +2,17 @@
#include <assert.h>
#include <drm_fourcc.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <wlr/util/log.h>
#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);