/*
 * Copyright © 2011 Benjamin Franzke
 * Copyright © 2010 Intel Corporation
 * Copyright © 2014,2018 Collabora Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

#include <xf86drm.h>
#include <gbm.h>

#include <wayland-client.h>
#include "shared/helpers.h"
#include "shared/platform.h"
#include "shared/weston-drm-fourcc.h"
#include <libweston/zalloc.h>
#include "xdg-shell-client-protocol.h"
#include "fullscreen-shell-unstable-v1-client-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "weston-direct-display-client-protocol.h"
#include "linux-explicit-synchronization-unstable-v1-client-protocol.h"

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include "shared/weston-egl-ext.h"

/* Possible options that affect the displayed image */
#define OPT_IMMEDIATE     (1 << 0)  /* create wl_buffer immediately */
#define OPT_IMPLICIT_SYNC (1 << 1)  /* force implicit sync */
#define OPT_MANDELBROT    (1 << 2)  /* render mandelbrot */
#define OPT_DIRECT_DISPLAY     (1 << 3)  /* direct-display */

#define MAX_BUFFER_PLANES 4

struct display {
	struct wl_display *display;
	struct wl_registry *registry;
	struct wl_compositor *compositor;
	struct xdg_wm_base *wm_base;
	struct zwp_fullscreen_shell_v1 *fshell;
	struct zwp_linux_dmabuf_v1 *dmabuf;
	struct weston_direct_display_v1 *direct_display;
	struct zwp_linux_explicit_synchronization_v1 *explicit_sync;
	uint32_t format;
	bool format_supported;
	uint64_t *modifiers;
	int modifiers_count;
	int req_dmabuf_immediate;
	bool use_explicit_sync;
	struct {
		EGLDisplay display;
		EGLContext context;
		EGLConfig conf;
		bool has_dma_buf_import_modifiers;
		bool has_no_config_context;
		PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers;
		PFNEGLCREATEIMAGEKHRPROC create_image;
		PFNEGLDESTROYIMAGEKHRPROC destroy_image;
		PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d;
		PFNEGLCREATESYNCKHRPROC create_sync;
		PFNEGLDESTROYSYNCKHRPROC destroy_sync;
		PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync;
		PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd;
		PFNEGLWAITSYNCKHRPROC wait_sync;
	} egl;
	struct {
		int drm_fd;
		struct gbm_device *device;
	} gbm;
};

struct buffer {
	struct display *display;
	struct wl_buffer *buffer;
	int busy;

	struct gbm_bo *bo;

	int width;
	int height;
	int format;
	uint64_t modifier;
	int plane_count;
	int dmabuf_fds[MAX_BUFFER_PLANES];
	uint32_t strides[MAX_BUFFER_PLANES];
	uint32_t offsets[MAX_BUFFER_PLANES];

	EGLImageKHR egl_image;
	GLuint gl_texture;
	GLuint gl_fbo;

	struct zwp_linux_buffer_release_v1 *buffer_release;
	/* The buffer owns the release_fence_fd, until it passes ownership
	 * to it to EGL (see wait_for_buffer_release_fence). */
	int release_fence_fd;
};

#define NUM_BUFFERS 3

struct window {
	struct display *display;
	int width, height;
	struct wl_surface *surface;
	struct xdg_surface *xdg_surface;
	struct xdg_toplevel *xdg_toplevel;
	struct zwp_linux_surface_synchronization_v1 *surface_sync;
	struct buffer buffers[NUM_BUFFERS];
	struct wl_callback *callback;
	bool initialized;
	bool wait_for_configure;
	struct {
		GLuint program;
		GLuint pos;
		GLuint color;
		GLuint offset_uniform;
	} gl;
	bool render_mandelbrot;
};

static sig_atomic_t running = 1;

static void
redraw(void *data, struct wl_callback *callback, uint32_t time);

static void
buffer_release(void *data, struct wl_buffer *buffer)
{
	struct buffer *mybuf = data;

	mybuf->busy = 0;
}

static const struct wl_buffer_listener buffer_listener = {
	buffer_release
};

static void
buffer_free(struct buffer *buf)
{
	int i;

	if (buf->release_fence_fd >= 0)
		close(buf->release_fence_fd);

	if (buf->buffer_release)
		zwp_linux_buffer_release_v1_destroy(buf->buffer_release);

	if (buf->gl_fbo)
		glDeleteFramebuffers(1, &buf->gl_fbo);

	if (buf->gl_texture)
		glDeleteTextures(1, &buf->gl_texture);

	if (buf->egl_image) {
		buf->display->egl.destroy_image(buf->display->egl.display,
						buf->egl_image);
	}

	if (buf->buffer)
		wl_buffer_destroy(buf->buffer);

	if (buf->bo)
		gbm_bo_destroy(buf->bo);

	for (i = 0; i < buf->plane_count; ++i) {
		if (buf->dmabuf_fds[i] >= 0)
			close(buf->dmabuf_fds[i]);
	}
}

static void
create_succeeded(void *data,
		 struct zwp_linux_buffer_params_v1 *params,
		 struct wl_buffer *new_buffer)
{
	struct buffer *buffer = data;

	buffer->buffer = new_buffer;
	/* When not using explicit synchronization listen to wl_buffer.release
	 * for release notifications, otherwise we are going to use
	 * zwp_linux_buffer_release_v1. */
	if (!buffer->display->use_explicit_sync) {
		wl_buffer_add_listener(buffer->buffer, &buffer_listener,
				       buffer);
	}

	zwp_linux_buffer_params_v1_destroy(params);
}

static void
create_failed(void *data, struct zwp_linux_buffer_params_v1 *params)
{
	struct buffer *buffer = data;

	buffer->buffer = NULL;
	running = 0;

	zwp_linux_buffer_params_v1_destroy(params);

	fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n");
}

static const struct zwp_linux_buffer_params_v1_listener params_listener = {
	create_succeeded,
	create_failed
};

static bool
create_fbo_for_buffer(struct display *display, struct buffer *buffer)
{
	static const int general_attribs = 3;
	static const int plane_attribs = 5;
	static const int entries_per_attrib = 2;
	EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) *
			entries_per_attrib + 1];
	unsigned int atti = 0;

	attribs[atti++] = EGL_WIDTH;
	attribs[atti++] = buffer->width;
	attribs[atti++] = EGL_HEIGHT;
	attribs[atti++] = buffer->height;
	attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
	attribs[atti++] = buffer->format;

#define ADD_PLANE_ATTRIBS(plane_idx) { \
	attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \
	attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \
	attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \
	attribs[atti++] = (int) buffer->offsets[plane_idx]; \
	attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \
	attribs[atti++] = (int) buffer->strides[plane_idx]; \
	if (display->egl.has_dma_buf_import_modifiers) { \
		attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \
		attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \
		attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \
		attribs[atti++] = buffer->modifier >> 32; \
	} \
	}

	if (buffer->plane_count > 0)
		ADD_PLANE_ATTRIBS(0);

	if (buffer->plane_count > 1)
		ADD_PLANE_ATTRIBS(1);

	if (buffer->plane_count > 2)
		ADD_PLANE_ATTRIBS(2);

	if (buffer->plane_count > 3)
		ADD_PLANE_ATTRIBS(3);

#undef ADD_PLANE_ATTRIBS

	attribs[atti] = EGL_NONE;

	assert(atti < ARRAY_LENGTH(attribs));

	buffer->egl_image = display->egl.create_image(display->egl.display,
						      EGL_NO_CONTEXT,
						      EGL_LINUX_DMA_BUF_EXT,
						      NULL, attribs);
	if (buffer->egl_image == EGL_NO_IMAGE_KHR) {
		fprintf(stderr, "EGLImageKHR creation failed\n");
		return false;
	}

	eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
			display->egl.context);

	glGenTextures(1, &buffer->gl_texture);
	glBindTexture(GL_TEXTURE_2D, buffer->gl_texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image);

	glGenFramebuffers(1, &buffer->gl_fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
			       GL_TEXTURE_2D, buffer->gl_texture, 0);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		fprintf(stderr, "FBO creation failed\n");
		return false;
	}

	return true;
}


static int
create_dmabuf_buffer(struct display *display, struct buffer *buffer,
		     int width, int height, uint32_t opts)
{
	/* Y-Invert the buffer image, since we are going to renderer to the
	 * buffer through a FBO. */
	static uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
	struct zwp_linux_buffer_params_v1 *params;
	int i;

	buffer->display = display;
	buffer->width = width;
	buffer->height = height;
	buffer->format = display->format;
	buffer->release_fence_fd = -1;

#ifdef HAVE_GBM_MODIFIERS
	if (display->modifiers_count > 0) {
		buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device,
							  buffer->width,
							  buffer->height,
							  buffer->format,
							  display->modifiers,
							  display->modifiers_count);
		if (buffer->bo)
			buffer->modifier = gbm_bo_get_modifier(buffer->bo);
	}
#endif

	if (!buffer->bo) {
		buffer->bo = gbm_bo_create(display->gbm.device,
					   buffer->width,
					   buffer->height,
					   buffer->format,
					   GBM_BO_USE_RENDERING);
		buffer->modifier = DRM_FORMAT_MOD_INVALID;
	}

	if (!buffer->bo) {
		fprintf(stderr, "create_bo failed\n");
		goto error;
	}

#ifdef HAVE_GBM_MODIFIERS
	buffer->plane_count = gbm_bo_get_plane_count(buffer->bo);
	for (i = 0; i < buffer->plane_count; ++i) {
		int ret;
		union gbm_bo_handle handle;

		handle = gbm_bo_get_handle_for_plane(buffer->bo, i);
		if (handle.s32 == -1) {
			fprintf(stderr, "error: failed to get gbm_bo_handle\n");
			goto error;
		}

		ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0,
					 &buffer->dmabuf_fds[i]);
		if (ret < 0 || buffer->dmabuf_fds[i] < 0) {
			fprintf(stderr, "error: failed to get dmabuf_fd\n");
			goto error;
		}
		buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i);
		buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i);
	}
#else
	buffer->plane_count = 1;
	buffer->strides[0] = gbm_bo_get_stride(buffer->bo);
	buffer->dmabuf_fds[0] = gbm_bo_get_fd(buffer->bo);
	if (buffer->dmabuf_fds[0] < 0) {
		fprintf(stderr, "error: failed to get dmabuf_fd\n");
		goto error;
	}
#endif

	params = zwp_linux_dmabuf_v1_create_params(display->dmabuf);

	if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) {
		weston_direct_display_v1_enable(display->direct_display, params);
		/* turn off Y_INVERT otherwise linux-dmabuf will reject it and
		 * we need all dmabuf flags turned off */
		flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;

		fprintf(stdout, "image is y-inverted as direct-display flag was set, "
				"dmabuf y-inverted attribute flag was removed\n");
	}

	for (i = 0; i < buffer->plane_count; ++i) {
		zwp_linux_buffer_params_v1_add(params,
					       buffer->dmabuf_fds[i],
					       i,
					       buffer->offsets[i],
					       buffer->strides[i],
					       buffer->modifier >> 32,
					       buffer->modifier & 0xffffffff);
	}

	zwp_linux_buffer_params_v1_add_listener(params, &params_listener, buffer);
	if (display->req_dmabuf_immediate) {
		buffer->buffer =
			zwp_linux_buffer_params_v1_create_immed(params,
								buffer->width,
								buffer->height,
								buffer->format,
								flags);
		/* When not using explicit synchronization listen to
		 * wl_buffer.release for release notifications, otherwise we
		 * are going to use zwp_linux_buffer_release_v1. */
		if (!buffer->display->use_explicit_sync) {
			wl_buffer_add_listener(buffer->buffer,
					       &buffer_listener,
					       buffer);
		}
	}
	else {
		zwp_linux_buffer_params_v1_create(params,
						  buffer->width,
						  buffer->height,
						  buffer->format,
						  flags);
	}

	if (!create_fbo_for_buffer(display, buffer))
		goto error;

	return 0;

error:
	buffer_free(buffer);
	return -1;
}

static void
xdg_surface_handle_configure(void *data, struct xdg_surface *surface,
			     uint32_t serial)
{
	struct window *window = data;

	xdg_surface_ack_configure(surface, serial);

	if (window->initialized && window->wait_for_configure)
		redraw(window, NULL, 0);
	window->wait_for_configure = false;
}

static const struct xdg_surface_listener xdg_surface_listener = {
	xdg_surface_handle_configure,
};

static void
xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel,
			      int32_t width, int32_t height,
			      struct wl_array *states)
{
}

static void
xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel)
{
	running = 0;
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
	xdg_toplevel_handle_configure,
	xdg_toplevel_handle_close,
};

static const char *vert_shader_text =
	"uniform float offset;\n"
	"attribute vec4 pos;\n"
	"attribute vec4 color;\n"
	"varying vec4 v_color;\n"
	"void main() {\n"
	"  gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n"
	"  v_color = color;\n"
	"}\n";

static const char *frag_shader_text =
	"precision mediump float;\n"
	"varying vec4 v_color;\n"
	"void main() {\n"
	"  gl_FragColor = v_color;\n"
	"}\n";

static const char *vert_shader_mandelbrot_text =
	"uniform float offset;\n"
	"attribute vec4 pos;\n"
	"varying vec2 v_pos;\n"
	"void main() {\n"
	"  v_pos = pos.xy;\n"
	"  gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n"
	"}\n";


/* Mandelbrot set shader using the escape time algorithm. */
static const char *frag_shader_mandelbrot_text =
	"precision mediump float;\n"
	"varying vec2 v_pos;\n"
	"void main() {\n"
	"  const int max_iteration = 500;\n"
	"  // Scale and translate position to get a nice mandelbrot drawing for\n"
	"  // the used v_pos x and y range (-0.5 to 0.5).\n"
	"  float x0 = 3.0 * v_pos.x - 0.5;\n"
	"  float y0 = 3.0 * v_pos.y;\n"
	"  float x = 0.0;\n"
	"  float y = 0.0;\n"
	"  int iteration = 0;\n"
	"  while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n"
	"    float xtemp = x * x - y * y + x0;\n"
	"    y = 2.0 * x * y + y0;\n"
	"    x = xtemp;\n"
	"    ++iteration;\n"
	"  }\n"
	"  float red = iteration == max_iteration ?\n"
	"              0.0 : 1.0 - fract(float(iteration) / 20.0);\n"
	"  gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n"
	"}\n";

static GLuint
create_shader(const char *source, GLenum shader_type)
{
	GLuint shader;
	GLint status;

	shader = glCreateShader(shader_type);
	assert(shader != 0);

	glShaderSource(shader, 1, (const char **) &source, NULL);
	glCompileShader(shader);

	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (!status) {
		char log[1000];
		GLsizei len;
		glGetShaderInfoLog(shader, 1000, &len, log);
		fprintf(stderr, "Error: compiling %s: %.*s\n",
			shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment",
			len, log);
		return 0;
	}

	return shader;
}

static GLuint
create_and_link_program(GLuint vert, GLuint frag)
{
	GLint status;
	GLuint program = glCreateProgram();

	glAttachShader(program, vert);
	glAttachShader(program, frag);
	glLinkProgram(program);

	glGetProgramiv(program, GL_LINK_STATUS, &status);
	if (!status) {
		char log[1000];
		GLsizei len;
		glGetProgramInfoLog(program, 1000, &len, log);
		fprintf(stderr, "Error: linking:\n%.*s\n", len, log);
		return 0;
	}

	return program;
}

static bool
window_set_up_gl(struct window *window)
{
	GLuint vert = create_shader(
		window->render_mandelbrot ? vert_shader_mandelbrot_text :
					    vert_shader_text,
		GL_VERTEX_SHADER);
	GLuint frag = create_shader(
		window->render_mandelbrot ? frag_shader_mandelbrot_text :
					    frag_shader_text,
		GL_FRAGMENT_SHADER);

	window->gl.program = create_and_link_program(vert, frag);

	glDeleteShader(vert);
	glDeleteShader(frag);

	window->gl.pos = glGetAttribLocation(window->gl.program, "pos");
	window->gl.color = glGetAttribLocation(window->gl.program, "color");

	glUseProgram(window->gl.program);

	window->gl.offset_uniform =
		glGetUniformLocation(window->gl.program, "offset");

	return window->gl.program != 0;
}

static void
destroy_window(struct window *window)
{
	int i;

	if (window->gl.program)
		glDeleteProgram(window->gl.program);

	if (window->callback)
		wl_callback_destroy(window->callback);

	for (i = 0; i < NUM_BUFFERS; i++) {
		if (window->buffers[i].buffer)
			buffer_free(&window->buffers[i]);
	}

	if (window->xdg_toplevel)
		xdg_toplevel_destroy(window->xdg_toplevel);
	if (window->xdg_surface)
		xdg_surface_destroy(window->xdg_surface);
	if (window->surface_sync)
		zwp_linux_surface_synchronization_v1_destroy(window->surface_sync);
	wl_surface_destroy(window->surface);
	free(window);
}

static struct window *
create_window(struct display *display, int width, int height, int opts)
{
	struct window *window;
	int i;
	int ret;

	window = zalloc(sizeof *window);
	if (!window)
		return NULL;

	window->callback = NULL;
	window->display = display;
	window->width = width;
	window->height = height;
	window->surface = wl_compositor_create_surface(display->compositor);

	if (display->wm_base) {
		window->xdg_surface =
			xdg_wm_base_get_xdg_surface(display->wm_base,
						    window->surface);

		assert(window->xdg_surface);

		xdg_surface_add_listener(window->xdg_surface,
					 &xdg_surface_listener, window);

		window->xdg_toplevel =
			xdg_surface_get_toplevel(window->xdg_surface);

		assert(window->xdg_toplevel);

		xdg_toplevel_add_listener(window->xdg_toplevel,
					  &xdg_toplevel_listener, window);

		xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-egl");

		window->wait_for_configure = true;
		wl_surface_commit(window->surface);
	} else if (display->fshell) {
		zwp_fullscreen_shell_v1_present_surface(display->fshell,
							window->surface,
							ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT,
							NULL);
	} else {
		assert(0);
	}

	if (display->explicit_sync) {
		window->surface_sync =
			zwp_linux_explicit_synchronization_v1_get_synchronization(
					display->explicit_sync, window->surface);
		assert(window->surface_sync);
	}

	for (i = 0; i < NUM_BUFFERS; ++i) {
		int j;
		for (j = 0; j < MAX_BUFFER_PLANES; ++j)
			window->buffers[i].dmabuf_fds[j] = -1;

	}

	for (i = 0; i < NUM_BUFFERS; ++i) {
		ret = create_dmabuf_buffer(display, &window->buffers[i],
		                           width, height, opts);

		if (ret < 0)
			goto error;
	}

	window->render_mandelbrot = opts & OPT_MANDELBROT;

	if (!window_set_up_gl(window))
		goto error;

	return window;

error:
	if (window)
		destroy_window(window);

	return NULL;
}

static int
create_egl_fence_fd(struct window *window)
{
	struct display *d = window->display;
	EGLSyncKHR sync = d->egl.create_sync(d->egl.display,
					     EGL_SYNC_NATIVE_FENCE_ANDROID,
					     NULL);
	int fd;

	assert(sync != EGL_NO_SYNC_KHR);
	/* We need to flush before we can get the fence fd. */
	glFlush();
	fd = d->egl.dup_native_fence_fd(d->egl.display, sync);
	assert(fd >= 0);

	d->egl.destroy_sync(d->egl.display, sync);

	return fd;
}

static struct buffer *
window_next_buffer(struct window *window)
{
	int i;

	for (i = 0; i < NUM_BUFFERS; i++)
		if (!window->buffers[i].busy)
			return &window->buffers[i];

	return NULL;
}

static const struct wl_callback_listener frame_listener;

/* Renders a square moving from the lower left corner to the
 * upper right corner of the window. The square's vertices have
 * the following colors:
 *
 *  green +-----+ yellow
 *        |     |
 *        |     |
 *    red +-----+ blue
 */
static void
render(struct window *window, struct buffer *buffer)
{
	/* Complete a movement iteration in 5000 ms. */
	static const uint64_t iteration_ms = 5000;
	static const GLfloat verts[4][2] = {
		{ -0.5, -0.5 },
		{ -0.5,  0.5 },
		{  0.5, -0.5 },
		{  0.5,  0.5 }
	};
	static const GLfloat colors[4][3] = {
		{ 1, 0, 0 },
		{ 0, 1, 0 },
		{ 0, 0, 1 },
		{ 1, 1, 0 }
	};
	GLfloat offset;
	struct timeval tv;
	uint64_t time_ms;

	gettimeofday(&tv, NULL);
	time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000;

	/* Split time_ms in repeating windows of [0, iteration_ms) and map them
	 * to offsets in the [-0.5, 0.5) range. */
	offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5;

	/* Direct all GL draws to the buffer through the FBO */
	glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo);

	glViewport(0, 0, window->width, window->height);

	glUniform1f(window->gl.offset_uniform, offset);

	glClearColor(0.0,0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts);
	glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors);
	glEnableVertexAttribArray(window->gl.pos);
	glEnableVertexAttribArray(window->gl.color);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	glDisableVertexAttribArray(window->gl.pos);
	glDisableVertexAttribArray(window->gl.color);
}

static void
render_mandelbrot(struct window *window, struct buffer *buffer)
{
	/* Complete a movement iteration in 5000 ms. */
	static const uint64_t iteration_ms = 5000;
	/* Split drawing in a square grid consisting of grid_side * grid_side
	 * cells. */
	static const int grid_side = 4;
	GLfloat norm_cell_side = 1.0 / grid_side;
	int num_cells = grid_side * grid_side;
	GLfloat offset;
	struct timeval tv;
	uint64_t time_ms;
	int i;

	gettimeofday(&tv, NULL);
	time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000;

	/* Split time_ms in repeating windows of [0, iteration_ms) and map them
	 * to offsets in the [-0.5, 0.5) range. */
	offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5;

	/* Direct all GL draws to the buffer through the FBO */
	glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo);

	glViewport(0, 0, window->width, window->height);

	glUniform1f(window->gl.offset_uniform, offset);

	glClearColor(0.6, 0.6, 0.6, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);

	for (i = 0; i < num_cells; ++i) {
		/* Calculate the vertex coordinates of the current grid cell. */
		int row = i / grid_side;
		int col = i % grid_side;
		GLfloat left = -0.5 + norm_cell_side * col;
		GLfloat right = left + norm_cell_side;
		GLfloat top = 0.5 - norm_cell_side * row;
		GLfloat bottom = top - norm_cell_side;
		GLfloat verts[4][2] = {
			{ left,  bottom },
			{ left,  top },
			{ right, bottom },
			{ right, top }
		};

		/* ... and draw it. */
		glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts);
		glEnableVertexAttribArray(window->gl.pos);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

		glDisableVertexAttribArray(window->gl.pos);
	}
}

static void
buffer_fenced_release(void *data,
		      struct zwp_linux_buffer_release_v1 *release,
                      int32_t fence)
{
	struct buffer *buffer = data;

	assert(release == buffer->buffer_release);
	assert(buffer->release_fence_fd == -1);

	buffer->busy = 0;
	buffer->release_fence_fd = fence;
	zwp_linux_buffer_release_v1_destroy(buffer->buffer_release);
	buffer->buffer_release = NULL;
}

static void
buffer_immediate_release(void *data,
			 struct zwp_linux_buffer_release_v1 *release)
{
	struct buffer *buffer = data;

	assert(release == buffer->buffer_release);
	assert(buffer->release_fence_fd == -1);

	buffer->busy = 0;
	zwp_linux_buffer_release_v1_destroy(buffer->buffer_release);
	buffer->buffer_release = NULL;
}

static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = {
       buffer_fenced_release,
       buffer_immediate_release,
};

static void
wait_for_buffer_release_fence(struct buffer *buffer)
{
	struct display *d = buffer->display;
	EGLint attrib_list[] = {
		EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd,
		EGL_NONE,
	};
	EGLSyncKHR sync = d->egl.create_sync(d->egl.display,
					     EGL_SYNC_NATIVE_FENCE_ANDROID,
					     attrib_list);
	int ret;

	assert(sync);

	/* EGLSyncKHR takes ownership of the fence fd. */
	buffer->release_fence_fd = -1;

	if (d->egl.wait_sync)
		ret = d->egl.wait_sync(d->egl.display, sync, 0);
	else
		ret = d->egl.client_wait_sync(d->egl.display, sync, 0,
					      EGL_FOREVER_KHR);
	assert(ret == EGL_TRUE);

	ret = d->egl.destroy_sync(d->egl.display, sync);
	assert(ret == EGL_TRUE);
}

static void
redraw(void *data, struct wl_callback *callback, uint32_t time)
{
	struct window *window = data;
	struct buffer *buffer;

	buffer = window_next_buffer(window);
	if (!buffer) {
		fprintf(stderr,
			!callback ? "Failed to create the first buffer.\n" :
			"All buffers busy at redraw(). Server bug?\n");
		abort();
	}

	if (buffer->release_fence_fd >= 0)
		wait_for_buffer_release_fence(buffer);

	if (window->render_mandelbrot)
		render_mandelbrot(window, buffer);
	else
		render(window, buffer);

	if (window->display->use_explicit_sync) {
		int fence_fd = create_egl_fence_fd(window);
		zwp_linux_surface_synchronization_v1_set_acquire_fence(
			window->surface_sync, fence_fd);
		close(fence_fd);

		buffer->buffer_release =
			zwp_linux_surface_synchronization_v1_get_release(window->surface_sync);
		zwp_linux_buffer_release_v1_add_listener(
			buffer->buffer_release, &buffer_release_listener, buffer);
	} else {
		glFinish();
	}

	wl_surface_attach(window->surface, buffer->buffer, 0, 0);
	wl_surface_damage(window->surface, 0, 0, window->width, window->height);

	if (callback)
		wl_callback_destroy(callback);

	window->callback = wl_surface_frame(window->surface);
	wl_callback_add_listener(window->callback, &frame_listener, window);
	wl_surface_commit(window->surface);
	buffer->busy = 1;
}

static const struct wl_callback_listener frame_listener = {
	redraw
};

static void
dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf,
		 uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo)
{
	struct display *d = data;
	uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo;

	if (format != d->format) {
		return;
	}

	d->format_supported = true;

	if (modifier != DRM_FORMAT_MOD_INVALID) {
		++d->modifiers_count;
		d->modifiers = realloc(d->modifiers,
				       d->modifiers_count * sizeof(*d->modifiers));
		d->modifiers[d->modifiers_count - 1] = modifier;
	}
}

static void
dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format)
{
	/* XXX: deprecated */
}

static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
	dmabuf_format,
	dmabuf_modifiers
};

static void
xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
{
	xdg_wm_base_pong(wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
	xdg_wm_base_ping,
};

static void
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") == 0) {
		d->compositor =
			wl_registry_bind(registry,
					 id, &wl_compositor_interface, 1);
	} else if (strcmp(interface, "xdg_wm_base") == 0) {
		d->wm_base = wl_registry_bind(registry,
					      id, &xdg_wm_base_interface, 1);
		xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d);
	} else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) {
		d->fshell = wl_registry_bind(registry,
					     id, &zwp_fullscreen_shell_v1_interface, 1);
	} else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
		if (version < 3)
			return;
		d->dmabuf = wl_registry_bind(registry,
					     id, &zwp_linux_dmabuf_v1_interface, 3);
		zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d);
	} else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) {
		d->explicit_sync = wl_registry_bind(
			registry, id,
			&zwp_linux_explicit_synchronization_v1_interface, 1);
	} else if (strcmp(interface, "weston_direct_display_v1") == 0) {
		d->direct_display = wl_registry_bind(registry,
						     id, &weston_direct_display_v1_interface, 1);
	}
}

static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
			      uint32_t name)
{
}

static const struct wl_registry_listener registry_listener = {
	registry_handle_global,
	registry_handle_global_remove
};

static void
destroy_display(struct display *display)
{
	if (display->gbm.device)
		gbm_device_destroy(display->gbm.device);

	if (display->gbm.drm_fd >= 0)
		close(display->gbm.drm_fd);

	if (display->egl.context != EGL_NO_CONTEXT)
		eglDestroyContext(display->egl.display, display->egl.context);

	if (display->egl.display != EGL_NO_DISPLAY)
		eglTerminate(display->egl.display);

	free(display->modifiers);

	if (display->dmabuf)
		zwp_linux_dmabuf_v1_destroy(display->dmabuf);

	if (display->wm_base)
		xdg_wm_base_destroy(display->wm_base);

	if (display->fshell)
		zwp_fullscreen_shell_v1_release(display->fshell);

	if (display->compositor)
		wl_compositor_destroy(display->compositor);

	if (display->registry)
		wl_registry_destroy(display->registry);

	if (display->display) {
		wl_display_flush(display->display);
		wl_display_disconnect(display->display);
	}

	free(display);
}

static bool
display_set_up_egl(struct display *display)
{
	static const EGLint context_attribs[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};
	EGLint major, minor, ret, count;
	const char *egl_extensions = NULL;
	const char *gl_extensions = NULL;

	EGLint config_attribs[] = {
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_RED_SIZE, 1,
		EGL_GREEN_SIZE, 1,
		EGL_BLUE_SIZE, 1,
		EGL_ALPHA_SIZE, 1,
		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
		EGL_NONE
	};

	display->egl.display =
		weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR,
						display->gbm.device, NULL);
	if (display->egl.display == EGL_NO_DISPLAY) {
		fprintf(stderr, "Failed to create EGLDisplay\n");
		goto error;
	}

	if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) {
		fprintf(stderr, "Failed to initialize EGLDisplay\n");
		goto error;
	}

	if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
		fprintf(stderr, "Failed to bind OpenGL ES API\n");
		goto error;
	}

	egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS);
	assert(egl_extensions != NULL);

	if (!weston_check_egl_extension(egl_extensions,
					"EGL_EXT_image_dma_buf_import")) {
		fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n");
		goto error;
	}

	if (!weston_check_egl_extension(egl_extensions,
					"EGL_KHR_surfaceless_context")) {
		fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n");
		goto error;
	}

	if (weston_check_egl_extension(egl_extensions,
					"EGL_KHR_no_config_context")) {
		display->egl.has_no_config_context = true;
	}

	if (display->egl.has_no_config_context) {
		display->egl.conf = EGL_NO_CONFIG_KHR;
	} else {
		fprintf(stderr,
			"Warning: EGL_KHR_no_config_context not supported\n");
		ret = eglChooseConfig(display->egl.display, config_attribs,
			      &display->egl.conf, 1, &count);
		assert(ret && count >= 1);
	}

	display->egl.context = eglCreateContext(display->egl.display,
						display->egl.conf,
						EGL_NO_CONTEXT,
						context_attribs);
	if (display->egl.context == EGL_NO_CONTEXT) {
		fprintf(stderr, "Failed to create EGLContext\n");
		goto error;
	}

	eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
		       display->egl.context);

	gl_extensions = (const char *) glGetString(GL_EXTENSIONS);
	assert(gl_extensions != NULL);

	if (!weston_check_egl_extension(gl_extensions,
					"GL_OES_EGL_image")) {
		fprintf(stderr, "GL_OES_EGL_image not supported\n");
		goto error;
	}

	if (weston_check_egl_extension(egl_extensions,
				       "EGL_EXT_image_dma_buf_import_modifiers")) {
		display->egl.has_dma_buf_import_modifiers = true;
		display->egl.query_dma_buf_modifiers =
			(void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT");
		assert(display->egl.query_dma_buf_modifiers);
	}

	display->egl.create_image =
		(void *) eglGetProcAddress("eglCreateImageKHR");
	assert(display->egl.create_image);

	display->egl.destroy_image =
		(void *) eglGetProcAddress("eglDestroyImageKHR");
	assert(display->egl.destroy_image);

	display->egl.image_target_texture_2d =
		(void *) eglGetProcAddress("glEGLImageTargetTexture2DOES");
	assert(display->egl.image_target_texture_2d);

	if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") &&
	    weston_check_egl_extension(egl_extensions,
				       "EGL_ANDROID_native_fence_sync")) {
		display->egl.create_sync =
			(void *) eglGetProcAddress("eglCreateSyncKHR");
		assert(display->egl.create_sync);

		display->egl.destroy_sync =
			(void *) eglGetProcAddress("eglDestroySyncKHR");
		assert(display->egl.destroy_sync);

		display->egl.client_wait_sync =
			(void *) eglGetProcAddress("eglClientWaitSyncKHR");
		assert(display->egl.client_wait_sync);

		display->egl.dup_native_fence_fd =
			(void *) eglGetProcAddress("eglDupNativeFenceFDANDROID");
		assert(display->egl.dup_native_fence_fd);
	}

	if (weston_check_egl_extension(egl_extensions,
				       "EGL_KHR_wait_sync")) {
		display->egl.wait_sync =
			(void *) eglGetProcAddress("eglWaitSyncKHR");
		assert(display->egl.wait_sync);
	}

	return true;

error:
	return false;
}

static bool
display_update_supported_modifiers_for_egl(struct display *d)
{
	uint64_t *egl_modifiers = NULL;
	int num_egl_modifiers = 0;
	EGLBoolean ret;
	int i;
	bool try_modifiers = d->egl.has_dma_buf_import_modifiers;

	if (try_modifiers) {
		ret = d->egl.query_dma_buf_modifiers(d->egl.display,
						     d->format,
						     0,    /* max_modifiers */
						     NULL, /* modifiers */
						     NULL, /* external_only */
						     &num_egl_modifiers);
		if (ret == EGL_FALSE) {
			fprintf(stderr, "Failed to query num EGL modifiers for format\n");
			goto error;
		}
	}

	if (!num_egl_modifiers)
		try_modifiers = false;

	/* If EGL doesn't support modifiers, don't use them at all. */
	if (!try_modifiers) {
		d->modifiers_count = 0;
		free(d->modifiers);
		d->modifiers = NULL;
		return true;
	}

	egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers));

	ret = d->egl.query_dma_buf_modifiers(d->egl.display,
					     d->format,
					     num_egl_modifiers,
					     egl_modifiers,
					     NULL, /* external_only */
					     &num_egl_modifiers);
	if (ret == EGL_FALSE) {
		fprintf(stderr, "Failed to query EGL modifiers for format\n");
		goto error;
	}

	/* Poor person's set intersection: d->modifiers INTERSECT
	 * egl_modifiers.  If a modifier is not supported, replace it with
	 * DRM_FORMAT_MOD_INVALID in the d->modifiers array.
	 */
	for (i = 0; i < d->modifiers_count; ++i) {
		uint64_t mod = d->modifiers[i];
		bool egl_supported = false;
		int j;

		for (j = 0; j < num_egl_modifiers; ++j) {
			if (egl_modifiers[j] == mod) {
				egl_supported = true;
				break;
			}
		}

		if (!egl_supported)
			d->modifiers[i] = DRM_FORMAT_MOD_INVALID;
	}

	free(egl_modifiers);

	return true;

error:
	free(egl_modifiers);

	return false;
}

static bool
display_set_up_gbm(struct display *display, char const* drm_render_node)
{
	display->gbm.drm_fd = open(drm_render_node, O_RDWR);
	if (display->gbm.drm_fd < 0) {
		fprintf(stderr, "Failed to open drm render node %s\n",
			drm_render_node);
		return false;
	}

	display->gbm.device = gbm_create_device(display->gbm.drm_fd);
	if (display->gbm.device == NULL) {
		fprintf(stderr, "Failed to create gbm device\n");
		return false;
	}

	return true;
}

static struct display *
create_display(char const *drm_render_node, uint32_t format, int opts)
{
	struct display *display = NULL;

	display = zalloc(sizeof *display);
	if (display == NULL) {
		fprintf(stderr, "out of memory\n");
		goto error;
	}

	display->gbm.drm_fd = -1;

	display->display = wl_display_connect(NULL);
	assert(display->display);

	display->format = format;
	display->req_dmabuf_immediate = opts & OPT_IMMEDIATE;

	display->registry = wl_display_get_registry(display->display);
	wl_registry_add_listener(display->registry,
				 &registry_listener, display);
	wl_display_roundtrip(display->display);
	if (display->dmabuf == NULL) {
		fprintf(stderr, "No zwp_linux_dmabuf global\n");
		goto error;
	}

	wl_display_roundtrip(display->display);

	if (!display->format_supported) {
		fprintf(stderr, "format 0x%"PRIX32" is not available\n",
			display->format);
		goto error;
	}

	/* GBM needs to be initialized before EGL, so that we have a valid
	 * render node gbm_device to create the EGL display from. */
	if (!display_set_up_gbm(display, drm_render_node))
		goto error;

	if (!display_set_up_egl(display))
		goto error;

	if (!display_update_supported_modifiers_for_egl(display))
		goto error;

	/* We use explicit synchronization only if the user hasn't disabled it,
	 * the compositor supports it, we can handle fence fds. */
	display->use_explicit_sync =
		!(opts & OPT_IMPLICIT_SYNC) &&
		display->explicit_sync &&
		display->egl.dup_native_fence_fd;

	if (opts & OPT_IMPLICIT_SYNC) {
		fprintf(stderr, "Warning: Not using explicit sync, disabled by user\n");
	} else if (!display->explicit_sync) {
		fprintf(stderr,
			"Warning: zwp_linux_explicit_synchronization_v1 not supported,\n"
			"         will not use explicit synchronization\n");
	} else if (!display->egl.dup_native_fence_fd) {
		fprintf(stderr,
			"Warning: EGL_ANDROID_native_fence_sync not supported,\n"
			"         will not use explicit synchronization\n");
	} else if (!display->egl.wait_sync) {
		fprintf(stderr,
			"Warning: EGL_KHR_wait_sync not supported,\n"
			"         will not use server-side wait\n");
	}

	return display;

error:
	if (display != NULL)
		destroy_display(display);
	return NULL;
}

static void
signal_int(int signum)
{
	running = 0;
}

static void
print_usage_and_exit(void)
{
	printf("usage flags:\n"
		"\t'-i,--import-immediate=<>'"
		"\n\t\t0 to import dmabuf via roundtrip, "
		"\n\t\t1 to enable import without roundtrip\n"
		"\t'-d,--drm-render-node=<>'"
		"\n\t\tthe full path to the drm render node to use\n"
		"\t'-s,--size=<>'"
		"\n\t\tthe window size in pixels (default: 256)\n"
		"\t'-e,--explicit-sync=<>'"
		"\n\t\t0 to disable explicit sync, "
		"\n\t\t1 to enable explicit sync (default: 1)\n"
		"\t'-f,--format=0x<>'"
		"\n\t\tthe DRM format code to use\n"
		"\t'-m,--mandelbrot'"
		"\n\t\trender a mandelbrot set with multiple draw calls\n"
		"\t'-g,--direct-display'"
		"\n\t\tenables weston-direct-display extension to attempt "
		"direct scan-out;\n\t\tnote this will cause the image to be "
		"displayed inverted as GL uses a\n\t\tdifferent texture "
		"coordinate system\n");
	exit(0);
}

static int
is_true(const char* c)
{
	if (!strcmp(c, "1"))
		return 1;
	else if (!strcmp(c, "0"))
		return 0;
	else
		print_usage_and_exit();

	return 0;
}

int
main(int argc, char **argv)
{
	struct sigaction sigint;
	struct display *display;
	struct window *window;
	uint32_t format = DRM_FORMAT_XRGB8888;
	int opts = 0;
	char const *drm_render_node = "/dev/dri/renderD128";
	int c, option_index, ret = 0;
	int window_size = 256;

	static struct option long_options[] = {
		{"import-immediate", required_argument, 0,  'i' },
		{"drm-render-node",  required_argument, 0,  'd' },
		{"size",	     required_argument, 0,  's' },
		{"explicit-sync",    required_argument, 0,  'e' },
		{"format",           required_argument, 0,  'f' },
		{"mandelbrot",       no_argument,	0,  'm' },
		{"direct-display",   no_argument,	0,  'g' },
		{"help",             no_argument      , 0,  'h' },
		{0, 0, 0, 0}
	};

	while ((c = getopt_long(argc, argv, "hi:d:s:e:f:mg",
				long_options, &option_index)) != -1) {
		switch (c) {
		case 'i':
			if (is_true(optarg))
				opts |= OPT_IMMEDIATE;
			break;
		case 'd':
			drm_render_node = optarg;
			break;
		case 's':
			window_size = strtol(optarg, NULL, 10);
			break;
		case 'e':
			if (!is_true(optarg))
				opts |= OPT_IMPLICIT_SYNC;
			break;
		case 'm':
			opts |= OPT_MANDELBROT;
			break;
		case 'g':
			opts |= OPT_DIRECT_DISPLAY;
			break;
		case 'f':
			format = strtoul(optarg, NULL, 0);
			break;
		default:
			print_usage_and_exit();
		}
	}

	display = create_display(drm_render_node, format, opts);
	if (!display)
		return 1;
	window = create_window(display, window_size, window_size, opts);
	if (!window)
		return 1;

	sigint.sa_handler = signal_int;
	sigemptyset(&sigint.sa_mask);
	sigint.sa_flags = SA_RESETHAND;
	sigaction(SIGINT, &sigint, NULL);

	/* Here we retrieve the linux-dmabuf objects if executed without immed,
	 * or error */
	wl_display_roundtrip(display->display);

	if (!running)
		return 1;

	window->initialized = true;

	if (!window->wait_for_configure)
		redraw(window, NULL, 0);

	while (running && ret != -1)
		ret = wl_display_dispatch(display->display);

	fprintf(stderr, "simple-dmabuf-egl exiting\n");
	destroy_window(window);
	destroy_display(display);

	return 0;
}