Hi On Mon, Oct 21, 2013 at 11:32 PM, David Herrmann <dh.herrm...@gmail.com> wrote: > 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.
Btw., I got this working with i915 by allowing GEM_OPEN/GEM_FLINK on the render-node. So if someone else tests this, you might need the same hacks. I will try to find the code in mesa that requires this. David > --- > 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