From ed7f2651b60e2d2e401041502656cd9f954a56cf Mon Sep 17 00:00:00 2001 From: Simon Zeni Date: Wed, 27 Jan 2021 22:10:59 -0500 Subject: [PATCH] render: add DRM dumb buffer allocator --- include/render/drm_dumb_allocator.h | 37 +++++ render/allocator.c | 12 +- render/drm_dumb_allocator.c | 237 ++++++++++++++++++++++++++++ render/meson.build | 1 + 4 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 include/render/drm_dumb_allocator.h create mode 100644 render/drm_dumb_allocator.c diff --git a/include/render/drm_dumb_allocator.h b/include/render/drm_dumb_allocator.h new file mode 100644 index 00000000..ff10d54b --- /dev/null +++ b/include/render/drm_dumb_allocator.h @@ -0,0 +1,37 @@ +#ifndef RENDER_DRM_DUMB_ALLOCATOR_H +#define RENDER_DRM_DUMB_ALLOCATOR_H + +#include "render/allocator.h" + +#include + +struct wlr_drm_dumb_buffer { + struct wlr_buffer base; + struct wl_list link; // wlr_drm_dumb_allocator::buffers + + int drm_fd; // -1 if the allocator has been destroyed + struct wlr_dmabuf_attributes dmabuf; + + uint32_t format; + uint32_t handle; + uint32_t stride; + uint32_t width, height; + + uint64_t size; + void *data; +}; + +struct wlr_drm_dumb_allocator { + struct wlr_allocator base; + struct wl_list buffers; // wlr_drm_dumb_buffer::link + int drm_fd; +}; + +/** + * Creates a new drm dumb allocator from a DRM FD. + * + * Does not take ownership over the FD. + */ +struct wlr_allocator *wlr_drm_dumb_allocator_create(int fd); + +#endif diff --git a/render/allocator.c b/render/allocator.c index 6dd8592b..f06049ab 100644 --- a/render/allocator.c +++ b/render/allocator.c @@ -1,4 +1,3 @@ -#define _POSIX_C_SOURCE 200809L #include #include #include @@ -7,6 +6,7 @@ #include "render/allocator.h" #include "render/gbm_allocator.h" #include "render/shm_allocator.h" +#include "render/drm_dumb_allocator.h" #include "render/wlr_renderer.h" #include "types/wlr_buffer.h" @@ -43,6 +43,16 @@ struct wlr_allocator *allocator_autocreate_with_drm_fd( wlr_log(WLR_DEBUG, "Failed to create shm allocator"); } + uint32_t drm_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR; + if ((backend_caps & drm_caps) && (renderer_caps & drm_caps) + && drm_fd != -1) { + wlr_log(WLR_DEBUG, "Trying to create drm dumb allocator"); + if ((alloc = wlr_drm_dumb_allocator_create(drm_fd)) != NULL) { + return alloc; + } + wlr_log(WLR_DEBUG, "Failed to create drm dumb allocator"); + } + wlr_log(WLR_ERROR, "Failed to create allocator"); return NULL; } diff --git a/render/drm_dumb_allocator.c b/render/drm_dumb_allocator.c new file mode 100644 index 00000000..0bb642ca --- /dev/null +++ b/render/drm_dumb_allocator.c @@ -0,0 +1,237 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "render/drm_dumb_allocator.h" +#include "render/pixel_format.h" + +static const struct wlr_buffer_impl buffer_impl; + +static struct wlr_drm_dumb_buffer *drm_dumb_buffer_from_buffer( + struct wlr_buffer *wlr_buf) { + assert(wlr_buf->impl == &buffer_impl); + return (struct wlr_drm_dumb_buffer *)wlr_buf; +} + +static void finish_buffer(struct wlr_drm_dumb_buffer *buf) { + if (buf->data) { + munmap(buf->data, buf->size); + } + + wlr_dmabuf_attributes_finish(&buf->dmabuf); + + if (buf->drm_fd >= 0) { + struct drm_mode_destroy_dumb destroy = { .handle = buf->handle }; + if (drmIoctl(buf->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy)) { + wlr_log_errno(WLR_ERROR, "Failed to destroy DRM dumb buffer"); + } + } + + wl_list_remove(&buf->link); +} + +static struct wlr_drm_dumb_buffer *create_buffer( + struct wlr_drm_dumb_allocator *alloc, int width, int height, + const struct wlr_drm_format *format) { + struct wlr_drm_dumb_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + wl_list_insert(&alloc->buffers, &buffer->link); + + const struct wlr_pixel_format_info *info = + drm_get_pixel_format_info(format->format); + if (info == NULL) { + wlr_log(WLR_ERROR, "DRM format 0x%"PRIX32" not supported", + format->format); + goto create_err; + } + + struct drm_mode_create_dumb create = {0}; + create.width = (uint32_t)width; + create.height = (uint32_t)height; + create.bpp = info->bpp; + + if (drmIoctl(alloc->drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create DRM dumb buffer"); + goto create_err; + } + + buffer->width = create.width; + buffer->height = create.height; + + buffer->stride = create.pitch; + buffer->handle = create.handle; + buffer->format = format->format; + + buffer->drm_fd = alloc->drm_fd; + + struct drm_mode_map_dumb map = {0}; + map.handle = buffer->handle; + + if (drmIoctl(alloc->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to map DRM dumb buffer"); + goto create_destroy; + } + + buffer->data = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, + alloc->drm_fd, map.offset); + if (buffer->data == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "Failed to mmap DRM dumb buffer"); + goto create_destroy; + } + + buffer->size = create.size; + + memset(buffer->data, 0, create.size); + + int prime_fd; + if (drmPrimeHandleToFD(alloc->drm_fd, buffer->handle, DRM_CLOEXEC, + &prime_fd) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to get PRIME handle from GEM handle"); + goto create_destroy; + } + + buffer->dmabuf = (struct wlr_dmabuf_attributes){ + .width = buffer->width, + .height = buffer->height, + .format = format->format, + .modifier = DRM_FORMAT_MOD_INVALID, + .n_planes = 1, + .offset[0] = 0, + .stride[0] = buffer->stride, + .fd[0] = prime_fd, + }; + + wlr_log(WLR_DEBUG, "Allocated %"PRIu32"x%"PRIu32" DRM dumb buffer", + buffer->width, buffer->height); + + return buffer; + +create_destroy: + finish_buffer(buffer); +create_err: + free(buffer); + return NULL; +} + +static bool buffer_get_data_ptr(struct wlr_buffer *wlr_buffer, void **data, + uint32_t *format, size_t *stride) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + *data = buf->data; + *stride = buf->stride; + *format = buf->format; + return true; +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + memcpy(attribs, &buf->dmabuf, sizeof(buf->dmabuf)); + return true; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + finish_buffer(buf); + free(buf); +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_dmabuf = buffer_get_dmabuf, + .get_data_ptr = buffer_get_data_ptr, +}; + +static const struct wlr_allocator_interface allocator_impl; + +static struct wlr_drm_dumb_allocator *drm_dumb_alloc_from_alloc( + struct wlr_allocator *wlr_alloc) { + assert(wlr_alloc->impl == &allocator_impl); + return (struct wlr_drm_dumb_allocator *)wlr_alloc; +} + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_alloc, int width, int height, + const struct wlr_drm_format *drm_format) { + struct wlr_drm_dumb_allocator *alloc = drm_dumb_alloc_from_alloc(wlr_alloc); + struct wlr_drm_dumb_buffer *buffer = create_buffer(alloc, width, height, + drm_format); + if (buffer == NULL) { + return NULL; + } + return &buffer->base; +} + +static void allocator_destroy(struct wlr_allocator *wlr_alloc) { + struct wlr_drm_dumb_allocator *alloc = drm_dumb_alloc_from_alloc(wlr_alloc); + + struct wlr_drm_dumb_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &alloc->buffers, link) { + buf->drm_fd = -1; + wl_list_remove(&buf->link); + wl_list_init(&buf->link); + } + + close(alloc->drm_fd); + free(alloc); +} + +static const struct wlr_allocator_interface allocator_impl = { + .create_buffer = allocator_create_buffer, + .destroy = allocator_destroy, +}; + +struct wlr_allocator *wlr_drm_dumb_allocator_create(int fd) { + /* Re-open the DRM node to avoid GEM handle ref'counting issues. See: + * https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110 + * TODO: don't assume we have the permission to just open the DRM node, + * find another way to re-open it. + */ + char *path = drmGetDeviceNameFromFd2(fd); + int drm_fd = open(path, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM node %s", path); + free(path); + return NULL; + } + + uint64_t has_dumb = 0; + if (drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM capabilities"); + free(path); + return NULL; + } + + if (has_dumb == 0) { + wlr_log(WLR_ERROR, "DRM dumb buffers not supported"); + free(path); + return NULL; + } + + struct wlr_drm_dumb_allocator *alloc = calloc(1, sizeof(*alloc)); + if (alloc == NULL) { + free(path); + return NULL; + } + wlr_allocator_init(&alloc->base, &allocator_impl); + + alloc->drm_fd = drm_fd; + wl_list_init(&alloc->buffers); + + wlr_log(WLR_DEBUG, "Created DRM dumb allocator with node %s", path); + free(path); + return &alloc->base; +} diff --git a/render/meson.build b/render/meson.build index 0a53acdd..6d185113 100644 --- a/render/meson.build +++ b/render/meson.build @@ -15,6 +15,7 @@ wlr_files += files( 'swapchain.c', 'wlr_renderer.c', 'wlr_texture.c', + 'drm_dumb_allocator.c', ) egl = dependency('egl', required: 'gles2' in renderers)