[RFC] clients: add simple-dmabuf
David Herrmann
dh.herrmann at gmail.com
Mon Oct 21 23:32:31 CEST 2013
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.herrmann at 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
More information about the wayland-devel
mailing list