simple-dmabuf is an example client which shows how to write wayland clients that use render-nodes for hardware-accelerated rendering and pass the buffer via dmabuf to the compositor. No mesa/EGL extensions are needed! Instead we pass dmabufs as wl_shm buffers to the compositors. This allows us to use hardware-accelerated rendering even with fbdev or other backends. Hurray!
This still contains some hacks and is more a proof-of-concept than something useful. However, it shows what render-nodes allow us to do without modifying the compositor at all. This example requires a 3.12-rc kernel with drm.rnodes=1 on the kernel command line (must be set during boot!). Furthermore, this currently only works with drivers that implement dmabuf-mmap (which is rcar-du, shmob, omapdrm and others). I have some hacks which implement it for i915 (but flushing seems to not work..). I haven't succeeded in making rendering work with i915. I currently have no access to other drivers so any further testing/debugging welcome. We either still miss some render-nodes ioctls that are needed by mesa (but I didn't get any mesa errors..) or my i915-dmabuf-mmap is just wrong. If someone gets this working on a specific card (working means non-black window), please let me know. --- clients/.gitignore | 1 + clients/Makefile.am | 7 +- clients/simple-dmabuf.c | 675 ++++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 2 +- 4 files changed, 683 insertions(+), 2 deletions(-) create mode 100644 clients/simple-dmabuf.c diff --git a/clients/.gitignore b/clients/.gitignore index 23959cc..b77b537 100644 --- a/clients/.gitignore +++ b/clients/.gitignore @@ -11,6 +11,7 @@ weston-image weston-nested weston-nested-client weston-resizor +weston-simple-dmabuf weston-simple-egl weston-simple-shm weston-simple-touch diff --git a/clients/Makefile.am b/clients/Makefile.am index 4f9dc48..39dca7f 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -56,11 +56,16 @@ endif if BUILD_SIMPLE_EGL_CLIENTS simple_egl_clients_programs = \ - weston-simple-egl + weston-simple-egl \ + weston-simple-dmabuf weston_simple_egl_SOURCES = simple-egl.c weston_simple_egl_CPPFLAGS = $(SIMPLE_EGL_CLIENT_CFLAGS) weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm + +weston_simple_dmabuf_SOURCES = simple-dmabuf.c +weston_simple_dmabuf_CPPFLAGS = $(SIMPLE_EGL_CLIENT_CFLAGS) +weston_simple_dmabuf_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm endif if BUILD_CLIENTS diff --git a/clients/simple-dmabuf.c b/clients/simple-dmabuf.c new file mode 100644 index 0000000..570c929 --- /dev/null +++ b/clients/simple-dmabuf.c @@ -0,0 +1,675 @@ +/* + * Copyright © 2012-2013 David Herrmann <dh.herrm...@gmail.com> + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/* + * Simple dmabuf/render-nodes Client + * This is a simple example client which uses dmabuf to pass buffers via wl_shm + * (instead of wl_drm or other EGL extensions) to the compositor. To render the + * images, we use DRM render-nodes or software-rendering as fallback. + * + * Note that this is a very basic example how to use wl_shm to share buffers + * which were rendered using a dedicated GPU. No mesa/EGL internal extensions + * are required. + * However, this is *not* the recommended way to do it! The mesa/EGL extensions + * allow much better integration and control. This client serves as a + * proof-of-concept and generic HOWTO. + */ + +#define EGL_EGLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES + +#include <config.h> + +#include <assert.h> +#include <dirent.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <gbm.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#include <wayland-client.h> +#include <xf86drm.h> + +/* buffer objects */ + +struct buffer { + struct window *window; + struct wl_buffer *buffer; + void *shm_data; + + struct rnode_buffer *rnode; +}; + +struct rnode_buffer { + struct gbm_bo *gbm_bo; + int fd; + struct buffer buf; +}; + +/* window objects */ + +struct rnode_window { + struct gbm_surface *gbm_surface; + EGLSurface egl_surface; +}; + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + struct wl_callback *callback; + + struct rnode_window rnode; +}; + +/* display objects */ + +struct rnode_display { + int fd; + struct gbm_device *gbm_dev; + EGLDisplay egl_display; + EGLConfig egl_conf; + EGLContext egl_ctx; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shm *shm; + uint32_t formats; + + struct rnode_display rnode; +}; + +/* forward declarations */ + +static void buffer_init(struct buffer *buf, struct window *w, + struct rnode_buffer *b); +static void buffer_destroy(struct buffer *buf); + +/* + * Render Nodes + */ + +/* rnode_open() tries to find a render-node, open it and return an FD. This + * should really be done via udev_enumerate_*() APIs, but to avoid a udev + * dependency here, we just hack it up via a /dev/dri/ iterator. + * DON'T COPY THAT! It's an ugly hack! */ +static int rnode_open(void) +{ + DIR *dir; + struct dirent *e; + int r, fd; + char *p; + + dir = opendir("/dev/dri/"); + if (!dir) + error(1, errno, "cannot open /dev/dri/"); + + fd = -1; + while ((e = readdir(dir))) { + if (e->d_type != DT_CHR) + continue; + if (strncmp(e->d_name, "renderD", 7)) + continue; + + r = asprintf(&p, "/dev/dri/%s", e->d_name); + if (r < 0) + error(1, errno, "cannot allocate pathname"); + + r = open(p, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); + if (r < 0) { + free(p); + error(0, errno, "cannot open %s", p); + continue; + } + + fd = r; + fprintf(stderr, "using render node %s\n", p); + free(p); + break; + } + + if (fd < 0) + error(1, 0, "cannot open any render-node in /dev/dri/"); + + return fd; +} + +static void rnode_init(struct display *d) +{ + static const EGLint conf_att[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 0, + EGL_NONE, + }; + static const EGLint ctx_att[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLBoolean b; + EGLenum api; + EGLint major, minor, n; + + d->rnode.fd = rnode_open(); + d->rnode.gbm_dev = gbm_create_device(d->rnode.fd); + if (!d->rnode.gbm_dev) + error(1, errno, "cannot create gbm device"); + + d->rnode.egl_display = eglGetDisplay((EGLNativeDisplayType)d->rnode.gbm_dev); + if (!d->rnode.egl_display) + error(1, errno, "cannot create EGL display"); + + b = eglInitialize(d->rnode.egl_display, &major, &minor); + if (!b) + error(1, errno, "cannot initialize EGL"); + + fprintf(stderr, "EGL major/minor: %d.%d\n", major, minor); + fprintf(stderr, "EGL version: %s\n", + eglQueryString(d->rnode.egl_display, EGL_VERSION)); + fprintf(stderr, "EGL vendor: %s\n", + eglQueryString(d->rnode.egl_display, EGL_VENDOR)); + fprintf(stderr, "EGL extensions: %s\n", + eglQueryString(d->rnode.egl_display, EGL_EXTENSIONS)); + + api = EGL_OPENGL_ES_API; + b = eglBindAPI(api); + if (!b) + error(1, errno, "cannot bind OpenGLES API"); + + b = eglChooseConfig(d->rnode.egl_display, conf_att, &d->rnode.egl_conf, + 1, &n); + if (!b || n != 1) + error(1, errno, "cannot find suitable EGL config"); + + d->rnode.egl_ctx = eglCreateContext(d->rnode.egl_display, + d->rnode.egl_conf, + EGL_NO_CONTEXT, + ctx_att); + if (!d->rnode.egl_ctx) + error(1, errno, "cannot create EGL context"); +} + +static void rnode_destroy(struct display *d) +{ + eglMakeCurrent(d->rnode.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroyContext(d->rnode.egl_display, d->rnode.egl_ctx); + eglTerminate(d->rnode.egl_display); + gbm_device_destroy(d->rnode.gbm_dev); + close(d->rnode.fd); +} + +static void rnode_buffer_destroy_cb(struct gbm_bo *bo, void *data) +{ + struct rnode_buffer *b = data; + + if (!b) + return; + + buffer_destroy(&b->buf); + close(b->fd); + free(b); +} + +static struct rnode_buffer *rnode_bo_to_buffer(struct window *w, + struct gbm_bo *bo) +{ + struct display *d = w->display; + struct rnode_buffer *b; + int r; + + b = gbm_bo_get_user_data(bo); + if (b) + return b; + + b = calloc(1, sizeof(*b)); + if (!b) + error(1, errno, "cannot allocate buffer object"); + + b->gbm_bo = bo; + + r = drmPrimeHandleToFD(d->rnode.fd, gbm_bo_get_handle(bo).u32, + 0, &b->fd); + if (r < 0) + error(1, errno, "cannot get prime-fd for buffer handle"); + + buffer_init(&b->buf, w, b); + gbm_bo_set_user_data(bo, b, rnode_buffer_destroy_cb); + return b; +} + +static void rnode_window_create(struct window *w) +{ + struct display *d = w->display; + EGLBoolean b; + + w->rnode.gbm_surface = gbm_surface_create(d->rnode.gbm_dev, + w->width, w->height, + GBM_FORMAT_XRGB8888, + GBM_BO_USE_RENDERING); + if (!w->rnode.gbm_surface) + error(1, errno, "cannot create gbm surface"); + + w->rnode.egl_surface = eglCreateWindowSurface(d->rnode.egl_display, + d->rnode.egl_conf, + (EGLNativeWindowType)w->rnode.gbm_surface, + NULL); + if (!w->rnode.egl_surface) + error(1, errno, "cannot create EGL surface"); + + b = eglMakeCurrent(d->rnode.egl_display, + w->rnode.egl_surface, + w->rnode.egl_surface, + d->rnode.egl_ctx); + if (!b) + error(1, errno, "cannot activate EGL context"); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void rnode_window_destroy(struct window *w) +{ + struct display *d = w->display; + + eglMakeCurrent(d->rnode.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + d->rnode.egl_ctx); + eglDestroySurface(d->rnode.egl_display, w->rnode.egl_surface); + gbm_surface_destroy(w->rnode.gbm_surface); +} + +static struct buffer *rnode_window_swap(struct window *w) +{ + int r; + EGLBoolean eb; + struct display *d = w->display; + struct gbm_bo *bo; + struct rnode_buffer *b; + + r = gbm_surface_has_free_buffers(w->rnode.gbm_surface); + if (!r) + error(1, errno, "gbm surface has no free buffers left"); + + glFinish(); + eb = eglSwapBuffers(d->rnode.egl_display, w->rnode.egl_surface); + if (!eb) + error(1, errno, "cannot swap buffers"); + + bo = gbm_surface_lock_front_buffer(w->rnode.gbm_surface); + if (!bo) + error(1, errno, "cannot lock front buffer"); + + b = rnode_bo_to_buffer(w, bo); + return &b->buf; +} + +static void rnode_buffer_release(struct buffer *buf) +{ + struct rnode_buffer *b = buf->rnode; + struct window *w = buf->window; + + gbm_surface_release_buffer(w->rnode.gbm_surface, b->gbm_bo); +} + +/* + * Buffer Handling + */ + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *buf = data; + + rnode_buffer_release(buf); +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release, +}; + +static void buffer_init(struct buffer *buf, struct window *w, + struct rnode_buffer *b) +{ + struct wl_shm_pool *pool; + struct display *d = w->display; + unsigned int size, stride; + + buf->window = w; + buf->rnode = b; + + stride = w->width * 4; + size = stride * w->height; + + /* mmap() not needed, but could be used as software fallback */ + buf->shm_data = mmap(NULL, size, PROT_READ, MAP_SHARED, + b->fd, 0); + if (buf->shm_data == MAP_FAILED) + error(1, errno, "cannot mmap buffer"); + + pool = wl_shm_create_pool(d->shm, b->fd, size); + if (!pool) + error(1, errno, "cannot create wl_shm pool"); + + buf->buffer = wl_shm_pool_create_buffer(pool, 0, w->width, w->height, + stride, + WL_SHM_FORMAT_XRGB8888); + if (!buf->buffer) + error(1, errno, "cannot create wl_shm buffer"); + + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + + wl_shm_pool_destroy(pool); +} + +static void buffer_destroy(struct buffer *buf) +{ + struct window *w = buf->window; + + munmap(buf->shm_data, w->width * w->height * 4); + wl_buffer_destroy(buf->buffer); +} + +/* + * Window Handling + */ + +static void +window_handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +window_handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ +} + +static void +window_handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener window_shell_surface_listener = { + window_handle_ping, + window_handle_configure, + window_handle_popup_done, +}; + +static struct window *window_create(struct display *display) +{ + struct window *window; + + window = calloc(1, sizeof *window); + if (!window) + error(1, errno, "cannot allocate window object"); + + window->callback = NULL; + window->display = display; + window->width = 250; + window->height = 250; + + window->surface = wl_compositor_create_surface(display->compositor); + if (!window->surface) + error(1, errno, "cannot create window surface"); + + window->shell_surface = wl_shell_get_shell_surface(display->shell, + window->surface); + if (!window->shell_surface) + error(1, errno, "cannot create window shell surface"); + + wl_shell_surface_add_listener(window->shell_surface, + &window_shell_surface_listener, window); + wl_shell_surface_set_title(window->shell_surface, "simple-dmabuf"); + wl_shell_surface_set_toplevel(window->shell_surface); + + rnode_window_create(window); + + return window; +} + +static void window_destroy(struct window *window) +{ + if (window->callback) + wl_callback_destroy(window->callback); + + rnode_window_destroy(window); + + wl_shell_surface_destroy(window->shell_surface); + wl_surface_destroy(window->surface); + + free(window); +} + +static void paint(int padding, int width, int height, uint32_t time) +{ + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void +window_redraw(void *data, struct wl_callback *callback, uint32_t time); + +static const struct wl_callback_listener window_frame_listener = { + window_redraw, +}; + +static void +window_redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *w = data; + struct buffer *buf; + + paint(20, w->width, w->height, time); + + buf = rnode_window_swap(w); + + wl_surface_attach(w->surface, buf->buffer, 0, 0); + wl_surface_damage(w->surface, 20, 20, + w->width - 40, w->height - 40); + + if (callback) + wl_callback_destroy(callback); + + w->callback = wl_surface_frame(w->surface); + if (!w->callback) + error(1, errno, "cannot create wayland frame callback"); + + wl_callback_add_listener(w->callback, &window_frame_listener, w); + wl_surface_commit(w->surface); +} + +/* + * Display Handling + */ + +static void +display_shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + d->formats |= (1 << format); +} + +struct wl_shm_listener display_shm_listener = { + display_shm_format, +}; + +static void +display_registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, + uint32_t version) +{ + struct display *d = data; + + if (!strcmp(interface, "wl_compositor")) { + d->compositor = wl_registry_bind(registry, id, + &wl_compositor_interface, 1); + } else if (!strcmp(interface, "wl_shell")) { + d->shell = wl_registry_bind(registry, id, + &wl_shell_interface, 1); + } else if (!strcmp(interface, "wl_shm")) { + d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); + if (d->shm) + wl_shm_add_listener(d->shm, &display_shm_listener, d); + } +} + +static void +display_registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener display_registry_listener = { + display_registry_handle_global, + display_registry_handle_global_remove, +}; + +static struct display *display_create(void) +{ + struct display *display; + + display = calloc(1, sizeof(*display)); + if (!display) + error(1, ENOMEM, "cannot allocate display"); + + display->display = wl_display_connect(NULL); + if (!display->display) + error(1, errno, "cannot connect to wayland server"); + + display->registry = wl_display_get_registry(display->display); + if (!display->registry) + error(1, errno, "cannot allocate wayland registry"); + + wl_registry_add_listener(display->registry, + &display_registry_listener, + display); + + /* wait for globals */ + wl_display_roundtrip(display->display); + if (!display->shm || !display->shell || !display->compositor) + error(1, 0, "wayland server does not support wl_shm/wl_shell/wl_compositor"); + + /* wait for wl_shm formats */ + wl_display_roundtrip(display->display); + if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) + error(1, 0, "wayland server does not support xrgb32"); + + rnode_init(display); + + return display; +} + +static void display_destroy(struct display *display) +{ + rnode_destroy(display); + + wl_shm_destroy(display->shm); + wl_shell_destroy(display->shell); + wl_compositor_destroy(display->compositor); + wl_registry_destroy(display->registry); + + wl_display_flush(display->display); + wl_display_disconnect(display->display); + + free(display); +} + +/* + * Main + */ + +static int running = 1; + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +signal_setup(void) +{ + struct sigaction sigint; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + sigaction(SIGTERM, &sigint, NULL); + sigaction(SIGPIPE, &sigint, NULL); + sigaction(SIGHUP, &sigint, NULL); +} + +int +main(int argc, char **argv) +{ + struct display *display; + struct window *window; + int r; + + fprintf(stderr, "initialize..\n"); + + signal_setup(); + display = display_create(); + window = window_create(display); + + fprintf(stderr, "run..\n"); + + /* Initialise damage to full surface, so the padding gets painted */ + wl_surface_damage(window->surface, 0, 0, + window->width, window->height); + + window_redraw(window, NULL, 0); + + do { + r = wl_display_dispatch(display->display); + } while (running && r >= 0); + + fprintf(stderr, "exit..\n"); + + window_destroy(window); + display_destroy(display); + + return 0; +} diff --git a/configure.ac b/configure.ac index 950086d..3d7b9f5 100644 --- a/configure.ac +++ b/configure.ac @@ -282,7 +282,7 @@ AC_ARG_ENABLE(simple-egl-clients, AM_CONDITIONAL(BUILD_SIMPLE_EGL_CLIENTS, test "x$enable_simple_egl_clients" = "xyes") if test x$enable_simple_egl_clients = xyes; then PKG_CHECK_MODULES(SIMPLE_EGL_CLIENT, - [egl >= 7.10 glesv2 wayland-client wayland-egl wayland-cursor]) + [egl >= 7.10 gbm libdrm glesv2 wayland-client wayland-egl wayland-cursor]) fi AC_ARG_ENABLE(clients, [ --enable-clients],, enable_clients=yes) -- 1.8.4.1 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel