[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