diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h
index 467de8ab..db8a088e 100644
--- a/include/rootston/desktop.h
+++ b/include/rootston/desktop.h
@@ -7,6 +7,7 @@
 #include <wlr/types/wlr_compositor.h>
 #include <wlr/types/wlr_gamma_control.h>
 #include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_linux_dmabuf.h>
 #include <wlr/types/wlr_list.h>
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/types/wlr_output.h>
@@ -46,6 +47,7 @@ struct roots_desktop {
 	struct wlr_primary_selection_device_manager *primary_selection_device_manager;
 	struct wlr_idle *idle;
 	struct wlr_idle_inhibit_manager_v1 *idle_inhibit;
+	struct wlr_linux_dmabuf *linux_dmabuf;
 
 	struct wl_listener new_output;
 	struct wl_listener layout_change;
diff --git a/include/wlr/render.h b/include/wlr/render.h
index d1498b07..c3bf5c97 100644
--- a/include/wlr/render.h
+++ b/include/wlr/render.h
@@ -85,6 +85,7 @@ struct wlr_texture {
 	bool valid;
 	uint32_t format;
 	int width, height;
+	bool inverted_y;
 	struct wl_signal destroy_signal;
 	struct wl_resource *resource;
 };
@@ -122,6 +123,8 @@ bool wlr_texture_upload_drm(struct wlr_texture *tex,
 bool wlr_texture_upload_eglimage(struct wlr_texture *tex,
 	EGLImageKHR image, uint32_t width, uint32_t height);
 
+bool wlr_texture_upload_dmabuf(struct wlr_texture *tex,
+	struct wl_resource *dmabuf_resource);
 /**
  * Copies a rectangle of pixels from a wl_shm_buffer onto the texture. The
  * buffer is not accessed after this function returns. Under some circumstances,
diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h
index 97a28016..f05a9837 100644
--- a/include/wlr/render/egl.h
+++ b/include/wlr/render/egl.h
@@ -6,6 +6,7 @@
 #include <pixman.h>
 #include <stdbool.h>
 #include <wayland-server.h>
+#include <wlr/types/wlr_linux_dmabuf.h>
 
 struct wlr_egl {
 	EGLDisplay display;
@@ -18,6 +19,8 @@ struct wlr_egl {
 	struct {
 		bool buffer_age;
 		bool swap_buffers_with_damage;
+		bool dmabuf_import;
+		bool dmabuf_import_modifiers;
 	} egl_exts;
 
 	struct wl_display *wl_display;
@@ -61,6 +64,31 @@ EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window);
 EGLImageKHR wlr_egl_create_image(struct wlr_egl *egl,
 		EGLenum target, EGLClientBuffer buffer, const EGLint *attribs);
 
+/**
+ * Creates an egl image from the given dmabuf attributes. Check usability
+ * of the dmabuf with wlr_egl_check_import_dmabuf once first.
+ */
+EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+		struct wlr_dmabuf_buffer_attribs *attributes);
+
+/**
+ * Try to import the given dmabuf. On success return true false otherwise.
+ * If this succeeds the dmabuf can be used for rendering on a texture
+ */
+bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl,
+		struct wlr_dmabuf_buffer *dmabuf);
+
+/**
+ * Get the available dmabuf formats
+ */
+int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats);
+
+/**
+ * Get the available dmabuf modifiers for a given format
+ */
+int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format,
+		uint64_t **modifiers);
+
 /**
  * Destroys an egl image created with the given wlr_egl.
  */
diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h
index 03b8309f..c8b4c8eb 100644
--- a/include/wlr/render/interface.h
+++ b/include/wlr/render/interface.h
@@ -8,6 +8,7 @@
 #include <wlr/render.h>
 #include <wlr/types/wlr_box.h>
 #include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_linux_dmabuf.h>
 
 struct wlr_renderer_impl;
 
@@ -59,6 +60,8 @@ struct wlr_texture_impl {
 		struct wl_resource *drm_buf);
 	bool (*upload_eglimage)(struct wlr_texture *texture, EGLImageKHR image,
 		uint32_t width, uint32_t height);
+	bool (*upload_dmabuf)(struct wlr_texture *texture,
+		struct wl_resource *dmabuf_resource);
 	void (*get_matrix)(struct wlr_texture *state, float mat[static 9],
 		const float projection[static 9], int x, int y);
 	void (*get_buffer_size)(struct wlr_texture *texture,
diff --git a/include/wlr/types/wlr_linux_dmabuf.h b/include/wlr/types/wlr_linux_dmabuf.h
new file mode 100644
index 00000000..9d71e598
--- /dev/null
+++ b/include/wlr/types/wlr_linux_dmabuf.h
@@ -0,0 +1,84 @@
+#ifndef WLR_TYPES_WLR_LINUX_DMABUF_H
+#define WLR_TYPES_WLR_LINUX_DMABUF_H
+
+#define WLR_LINUX_DMABUF_MAX_PLANES 4
+
+#include <stdint.h>
+#include <wayland-server-protocol.h>
+
+/* So we don't have to pull in linux specific drm headers */
+#ifndef DRM_FORMAT_MOD_INVALID
+#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1)
+#endif
+
+struct wlr_dmabuf_buffer_attribs {
+	/* set via params_add */
+	int n_planes;
+	uint32_t offset[WLR_LINUX_DMABUF_MAX_PLANES];
+	uint32_t stride[WLR_LINUX_DMABUF_MAX_PLANES];
+	uint64_t modifier[WLR_LINUX_DMABUF_MAX_PLANES];
+	int fd[WLR_LINUX_DMABUF_MAX_PLANES];
+	/* set via params_create */
+	int32_t width;
+	int32_t height;
+	uint32_t format;
+	uint32_t flags; /* enum zlinux_buffer_params_flags */
+};
+
+struct wlr_dmabuf_buffer {
+	struct wlr_egl *egl;
+	struct wl_resource *buffer_resource;
+	struct wl_resource *params_resource;
+	struct wlr_dmabuf_buffer_attribs attributes;
+};
+
+/**
+ * Returns true if the given resource was created via the linux-dmabuf
+ * buffer protocol, false otherwise
+ */
+bool wlr_dmabuf_resource_is_buffer(struct wl_resource *buffer_resource);
+
+/**
+ * Returns the wlr_dmabuf_buffer if the given resource was created
+ * via the linux-dmabuf buffer protocol
+ */
+struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_buffer_resource(
+	struct wl_resource *buffer_resource);
+
+/**
+ * Returns the wlr_dmabuf_buffer if the given resource was created
+ * via the linux-dmabuf params protocol
+ */
+struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_params_resource(
+	struct wl_resource *params_resource);
+
+/**
+ * Returns true if the given dmabuf has y-axis inverted, false otherwise
+ */
+bool wlr_dmabuf_buffer_has_inverted_y(struct wlr_dmabuf_buffer *dmabuf);
+
+/* the protocol interface */
+struct wlr_linux_dmabuf {
+	struct wl_global *wl_global;
+	struct wl_listener display_destroy;
+	struct wlr_egl *egl;
+};
+
+/**
+ * Create linux-dmabuf interface
+ */
+struct wlr_linux_dmabuf *wlr_linux_dmabuf_create(struct wl_display *display,
+	struct wlr_egl *egl);
+/**
+ * Destroy the linux-dmabuf interface
+ */
+void wlr_linux_dmabuf_destroy(struct wlr_linux_dmabuf *linux_dmabuf);
+
+/**
+ * Returns the wlr_linux_dmabuf if the given resource was created
+ * via the linux_dmabuf protocol
+ */
+struct wlr_linux_dmabuf *wlr_linux_dmabuf_from_resource(
+	struct wl_resource *resource);
+
+#endif
diff --git a/protocol/meson.build b/protocol/meson.build
index 6c87a887..638b0c46 100644
--- a/protocol/meson.build
+++ b/protocol/meson.build
@@ -21,9 +21,10 @@ wayland_scanner_client = generator(
 )
 
 protocols = [
-	[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
 	[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
 	[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
+	[wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
+	[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
 	'gamma-control.xml',
 	'gtk-primary-selection.xml',
 	'idle.xml',
diff --git a/render/egl.c b/render/egl.c
index 0a68d6e5..55809983 100644
--- a/render/egl.c
+++ b/render/egl.c
@@ -167,6 +167,12 @@ bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display,
 		check_egl_ext(egl->egl_exts_str, "EGL_EXT_swap_buffers_with_damage") ||
 		check_egl_ext(egl->egl_exts_str, "EGL_KHR_swap_buffers_with_damage");
 
+	egl->egl_exts.dmabuf_import =
+		check_egl_ext(egl->egl_exts_str, "EGL_EXT_image_dma_buf_import");
+	egl->egl_exts.dmabuf_import_modifiers =
+		check_egl_ext(egl->egl_exts_str, "EGL_EXT_image_dma_buf_import_modifiers")
+		&& eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT;
+
 	return true;
 
 error:
@@ -299,3 +305,132 @@ bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface,
 	}
 	return true;
 }
+
+EGLImage wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+		struct wlr_dmabuf_buffer_attribs *attributes) {
+	int atti = 0;
+	EGLint attribs[20];
+	attribs[atti++] = EGL_WIDTH;
+	attribs[atti++] = attributes->width;
+	attribs[atti++] = EGL_HEIGHT;
+	attribs[atti++] = attributes->height;
+	attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+	attribs[atti++] = attributes->format;
+
+	bool has_modifier = false;
+	if (attributes->modifier[0] != DRM_FORMAT_MOD_INVALID) {
+		if (!egl->egl_exts.dmabuf_import_modifiers) {
+			return NULL;
+		}
+		has_modifier = true;
+	}
+
+	/* TODO: YUV planes have up four planes but we only support a
+	   single EGLImage for now */
+	if (attributes->n_planes > 1) {
+		return NULL;
+	}
+
+	attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+	attribs[atti++] = attributes->fd[0];
+	attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+	attribs[atti++] = attributes->offset[0];
+	attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+	attribs[atti++] = attributes->stride[0];
+	if (has_modifier) {
+		attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+		attribs[atti++] = attributes->modifier[0] & 0xFFFFFFFF;
+		attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+		attribs[atti++] = attributes->modifier[0] >> 32;
+	}
+	attribs[atti++] = EGL_NONE;
+	return eglCreateImageKHR(egl->display, EGL_NO_CONTEXT,
+		EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+}
+
+#ifndef DRM_FORMAT_BIG_ENDIAN
+# define DRM_FORMAT_BIG_ENDIAN 0x80000000
+#endif
+bool wlr_egl_check_import_dmabuf(struct wlr_egl *egl,
+		struct wlr_dmabuf_buffer *dmabuf) {
+	switch (dmabuf->attributes.format & ~DRM_FORMAT_BIG_ENDIAN) {
+		/* YUV based formats not yet supported */
+	case WL_SHM_FORMAT_YUYV:
+	case WL_SHM_FORMAT_YVYU:
+	case WL_SHM_FORMAT_UYVY:
+	case WL_SHM_FORMAT_VYUY:
+	case WL_SHM_FORMAT_AYUV:
+		return false;
+	default:
+		break;
+	}
+
+	EGLImage egl_image = wlr_egl_create_image_from_dmabuf(egl,
+		&dmabuf->attributes);
+	if (egl_image) {
+		/* We can import the image, good. No need to keep it
+		   since wlr_texture_upload_dmabuf will import it again */
+		wlr_egl_destroy_image(egl, egl_image);
+		return true;
+	}
+	/* TODO: import yuv dmabufs */
+	return false;
+}
+
+int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl,
+		int **formats) {
+	if (!egl->egl_exts.dmabuf_import ||
+		!egl->egl_exts.dmabuf_import_modifiers) {
+		wlr_log(L_ERROR, "dmabuf extension not present");
+		return -1;
+	}
+
+	EGLint num;
+	if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) {
+		wlr_log(L_ERROR, "failed to query number of dmabuf formats");
+		return -1;
+	}
+
+	*formats = calloc(num, sizeof(int));
+	if (*formats == NULL) {
+		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno));
+		return -1;
+	}
+
+	if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) {
+		wlr_log(L_ERROR, "failed to query dmabuf format");
+		free(*formats);
+		return -1;
+	}
+	return num;
+}
+
+int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl,
+		int format, uint64_t **modifiers) {
+	if (!egl->egl_exts.dmabuf_import ||
+		!egl->egl_exts.dmabuf_import_modifiers) {
+		wlr_log(L_ERROR, "dmabuf extension not present");
+		return -1;
+	}
+
+	EGLint num;
+	if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0,
+			NULL, NULL, &num)) {
+		wlr_log(L_ERROR, "failed to query dmabuf number of modifiers");
+		return -1;
+	}
+
+	*modifiers = calloc(num, sizeof(uint64_t));
+	if (*modifiers == NULL) {
+		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno));
+		return -1;
+	}
+
+	if (!eglQueryDmaBufModifiersEXT(egl->display, format, num,
+		*modifiers, NULL, &num)) {
+		wlr_log(L_ERROR, "failed to query dmabuf modifiers");
+		free(*modifiers);
+		return -1;
+	}
+	return num;
+}
diff --git a/render/glapi.txt b/render/glapi.txt
index 0b0b452c..02ac7dd8 100644
--- a/render/glapi.txt
+++ b/render/glapi.txt
@@ -8,3 +8,5 @@ eglCreatePlatformWindowSurfaceEXT
 -glEGLImageTargetTexture2DOES
 -eglSwapBuffersWithDamageEXT
 -eglSwapBuffersWithDamageKHR
+-eglQueryDmaBufFormatsEXT
+-eglQueryDmaBufModifiersEXT
diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c
index e0a98d29..0324ad64 100644
--- a/render/gles2/renderer.c
+++ b/render/gles2/renderer.c
@@ -182,7 +182,8 @@ static bool wlr_gles2_render_texture_with_matrix(
 
 	wlr_texture_bind(texture);
 	GL_CALL(glUniformMatrix3fv(0, 1, GL_FALSE, matrix));
-	GL_CALL(glUniform1f(2, alpha));
+	GL_CALL(glUniform1i(1, texture->inverted_y));
+	GL_CALL(glUniform1f(3, alpha));
 	draw_quad();
 	return true;
 }
diff --git a/render/gles2/shaders.c b/render/gles2/shaders.c
index 9d3a67c2..a64b941e 100644
--- a/render/gles2/shaders.c
+++ b/render/gles2/shaders.c
@@ -32,6 +32,7 @@ const GLchar quad_fragment_src[] =
 "precision mediump float;"
 "varying vec4 v_color;"
 "varying vec2 v_texcoord;"
+""
 "void main() {"
 "  gl_FragColor = v_color;"
 "}";
@@ -41,6 +42,7 @@ const GLchar ellipse_fragment_src[] =
 "precision mediump float;"
 "varying vec4 v_color;"
 "varying vec2 v_texcoord;"
+""
 "void main() {"
 "  float l = length(v_texcoord - vec2(0.5, 0.5));"
 "  if (l > 0.5) discard;"
@@ -50,6 +52,7 @@ const GLchar ellipse_fragment_src[] =
 // Textured quads
 const GLchar vertex_src[] =
 "uniform mat3 proj;"
+"uniform bool invert_y;"
 "attribute vec2 pos;"
 "attribute vec2 texcoord;"
 "varying vec2 v_texcoord;"
@@ -67,8 +70,12 @@ const GLchar vertex_src[] =
 "}"
 ""
 "void main() {"
-"	gl_Position = vec4(transpose(proj) * vec3(pos, 1.0), 1.0);"
-"	v_texcoord = texcoord;"
+"  gl_Position = vec4(transpose(proj) * vec3(pos, 1.0), 1.0);"
+"  if (invert_y) {"
+"    v_texcoord = vec2(texcoord.s, 1.0 - texcoord.t);"
+"  } else {"
+"    v_texcoord = texcoord;"
+"  }"
 "}";
 
 const GLchar fragment_src_rgba[] =
@@ -76,6 +83,7 @@ const GLchar fragment_src_rgba[] =
 "varying vec2 v_texcoord;"
 "uniform sampler2D tex;"
 "uniform float alpha;"
+""
 "void main() {"
 "	gl_FragColor = alpha * texture2D(tex, v_texcoord);"
 "}";
@@ -85,6 +93,7 @@ const GLchar fragment_src_rgbx[] =
 "varying vec2 v_texcoord;"
 "uniform sampler2D tex;"
 "uniform float alpha;"
+""
 "void main() {"
 "	gl_FragColor.rgb = alpha * texture2D(tex, v_texcoord).rgb;"
 "	gl_FragColor.a = alpha;"
@@ -95,6 +104,7 @@ const GLchar fragment_src_external[] =
 "precision mediump float;"
 "varying vec2 v_texcoord;"
 "uniform samplerExternalOES texture0;"
+""
 "void main() {"
 "	vec4 col = texture2D(texture0, v_texcoord);"
 "	gl_FragColor = vec4(col.rgb, col.a);"
diff --git a/render/gles2/texture.c b/render/gles2/texture.c
index 9ee2a3e3..875affe2 100644
--- a/render/gles2/texture.c
+++ b/render/gles2/texture.c
@@ -164,6 +164,7 @@ static bool gles2_texture_upload_drm(struct wlr_texture *_tex,
 
 	EGLint inverted_y;
 	wlr_egl_query_buffer(tex->egl, buf, EGL_WAYLAND_Y_INVERTED_WL, &inverted_y);
+	tex->wlr_texture.inverted_y = !!inverted_y;
 
 	GLenum target;
 	const struct pixel_format *pf;
@@ -226,6 +227,42 @@ static bool gles2_texture_upload_eglimage(struct wlr_texture *wlr_tex,
 	return true;
 }
 
+
+static bool gles2_texture_upload_dmabuf(struct wlr_texture *_tex,
+		struct wl_resource *dmabuf_resource) {
+	struct wlr_gles2_texture *tex = (struct wlr_gles2_texture *)_tex;
+	struct wlr_dmabuf_buffer *dmabuf = wlr_dmabuf_buffer_from_buffer_resource(
+		dmabuf_resource);
+
+	if (!tex->egl->egl_exts.dmabuf_import) {
+		wlr_log(L_ERROR, "Want dmabuf but extension not present");
+		return false;
+	}
+
+	tex->wlr_texture.width = dmabuf->attributes.width;
+	tex->wlr_texture.height = dmabuf->attributes.height;
+
+	if (tex->image) {
+		wlr_egl_destroy_image(tex->egl, tex->image);
+	}
+
+	if (wlr_dmabuf_buffer_has_inverted_y(dmabuf)) {
+		_tex->inverted_y = true;
+	}
+
+	GLenum target = GL_TEXTURE_2D;
+	const struct pixel_format *pf =
+		gl_format_for_wl_format(WL_SHM_FORMAT_ARGB8888);
+	gles2_texture_ensure_texture(tex);
+	GL_CALL(glBindTexture(target, tex->tex_id));
+	tex->image = wlr_egl_create_image_from_dmabuf(tex->egl, &dmabuf->attributes);
+	GL_CALL(glActiveTexture(GL_TEXTURE0));
+	GL_CALL(glEGLImageTargetTexture2DOES(target, tex->image));
+	tex->pixel_format = pf;
+	tex->wlr_texture.valid = true;
+	return true;
+}
+
 static void gles2_texture_get_matrix(struct wlr_texture *_texture,
 		float mat[static 9], const float projection[static 9], int x, int y) {
 	struct wlr_gles2_texture *texture = (struct wlr_gles2_texture *)_texture;
@@ -236,6 +273,21 @@ static void gles2_texture_get_matrix(struct wlr_texture *_texture,
 	wlr_matrix_multiply(mat, projection, mat);
 }
 
+
+static bool gles2_texture_get_dmabuf_size(struct wlr_texture *texture, struct
+		wl_resource *resource, int *width, int *height) {
+	if (!wlr_dmabuf_resource_is_buffer(resource)) {
+		return false;
+	}
+
+	struct wlr_dmabuf_buffer *dmabuf = wlr_dmabuf_buffer_from_buffer_resource(
+		resource);
+	*width = dmabuf->attributes.width;
+	*height = dmabuf->attributes.height;
+	return true;
+}
+
+
 static void gles2_texture_get_buffer_size(struct wlr_texture *texture, struct
 		wl_resource *resource, int *width, int *height) {
 	struct wl_shm_buffer *buffer = wl_shm_buffer_get(resource);
@@ -246,10 +298,12 @@ static void gles2_texture_get_buffer_size(struct wlr_texture *texture, struct
 		}
 		if (!wlr_egl_query_buffer(tex->egl, resource, EGL_WIDTH,
 				(EGLint*)width)) {
-			wlr_log(L_ERROR, "could not get size of the buffer "
-				"(no buffer found)");
-			return;
-		};
+			if (!gles2_texture_get_dmabuf_size(texture, resource,
+					width, height)) {
+				wlr_log(L_ERROR, "could not get size of the buffer");
+				return;
+			}
+		}
 		wlr_egl_query_buffer(tex->egl, resource, EGL_HEIGHT,
 			(EGLint*)height);
 
@@ -288,6 +342,7 @@ static struct wlr_texture_impl wlr_texture_impl = {
 	.upload_shm = gles2_texture_upload_shm,
 	.update_shm = gles2_texture_update_shm,
 	.upload_drm = gles2_texture_upload_drm,
+	.upload_dmabuf = gles2_texture_upload_dmabuf,
 	.upload_eglimage = gles2_texture_upload_eglimage,
 	.get_matrix = gles2_texture_get_matrix,
 	.get_buffer_size = gles2_texture_get_buffer_size,
diff --git a/render/wlr_texture.c b/render/wlr_texture.c
index 1ec9beb9..3685185a 100644
--- a/render/wlr_texture.c
+++ b/render/wlr_texture.c
@@ -53,6 +53,11 @@ bool wlr_texture_upload_eglimage(struct wlr_texture *texture,
 	return texture->impl->upload_eglimage(texture, image, width, height);
 }
 
+bool wlr_texture_upload_dmabuf(struct wlr_texture *texture,
+		struct wl_resource *dmabuf_resource) {
+	return texture->impl->upload_dmabuf(texture, dmabuf_resource);
+}
+
 void wlr_texture_get_matrix(struct wlr_texture *texture,
 		float mat[static 9], const float projection[static 9], int x, int y) {
 	texture->impl->get_matrix(texture, mat, projection, x, y);
diff --git a/rootston/desktop.c b/rootston/desktop.c
index 3628b051..a130fe38 100644
--- a/rootston/desktop.c
+++ b/rootston/desktop.c
@@ -9,6 +9,7 @@
 #include <wlr/types/wlr_cursor.h>
 #include <wlr/types/wlr_gamma_control.h>
 #include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_linux_dmabuf.h>
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/types/wlr_idle_inhibit_v1.h>
 #include <wlr/types/wlr_primary_selection.h>
@@ -729,6 +730,8 @@ struct roots_desktop *desktop_create(struct roots_server *server,
 	desktop->idle = wlr_idle_create(server->wl_display);
 	desktop->idle_inhibit = wlr_idle_inhibit_v1_create(server->wl_display);
 
+	struct wlr_egl *egl = wlr_backend_get_egl(server->backend);
+	desktop->linux_dmabuf = wlr_linux_dmabuf_create(server->wl_display, egl);
 	return desktop;
 }
 
diff --git a/types/meson.build b/types/meson.build
index 91b4326d..94993b52 100644
--- a/types/meson.build
+++ b/types/meson.build
@@ -10,6 +10,7 @@ lib_wlr_types = static_library(
 		'wlr_idle.c',
 		'wlr_input_device.c',
 		'wlr_keyboard.c',
+		'wlr_linux_dmabuf.c',
 		'wlr_list.c',
 		'wlr_matrix.c',
 		'wlr_output_damage.c',
diff --git a/types/wlr_linux_dmabuf.c b/types/wlr_linux_dmabuf.c
new file mode 100644
index 00000000..3b86166e
--- /dev/null
+++ b/types/wlr_linux_dmabuf.c
@@ -0,0 +1,464 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/render.h>
+#include <wlr/render/egl.h>
+#include <wlr/types/wlr_linux_dmabuf.h>
+#include <wlr/util/log.h>
+#include "linux-dmabuf-unstable-v1-protocol.h"
+
+static void wl_buffer_destroy(struct wl_client *client,
+		struct wl_resource *resource) {
+	wl_resource_destroy(resource);
+}
+
+static const struct wl_buffer_interface wl_buffer_impl = {
+	wl_buffer_destroy,
+};
+
+bool wlr_dmabuf_buffer_has_inverted_y(struct wlr_dmabuf_buffer *dmabuf) {
+	return dmabuf->attributes.flags
+		& ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
+}
+
+bool wlr_dmabuf_resource_is_buffer(struct wl_resource *buffer_resource) {
+	if (!wl_resource_instance_of(buffer_resource, &wl_buffer_interface,
+		&wl_buffer_impl)) {
+		return false;
+	}
+
+	struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(buffer_resource);
+	if (buffer && buffer->buffer_resource && !buffer->params_resource &&
+		buffer->buffer_resource == buffer_resource) {
+		return true;
+	}
+
+	return false;
+}
+
+struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_buffer_resource(
+		struct wl_resource *buffer_resource) {
+	assert(wl_resource_instance_of(buffer_resource, &wl_buffer_interface,
+			&wl_buffer_impl));
+
+	struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(buffer_resource);
+	assert(buffer);
+	assert(buffer->buffer_resource);
+	assert(!buffer->params_resource);
+	assert(buffer->buffer_resource == buffer_resource);
+
+	return buffer;
+}
+
+static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_buffer *buffer) {
+	for (int i = 0; i < buffer->attributes.n_planes; i++) {
+		close(buffer->attributes.fd[i]);
+		buffer->attributes.fd[i] = -1;
+	}
+	buffer->attributes.n_planes = 0;
+	free(buffer);
+}
+
+static void params_destroy(struct wl_client *client, struct wl_resource *resource) {
+	wl_resource_destroy(resource);
+}
+
+static void params_add(struct wl_client *client,
+		struct wl_resource *params_resource, int32_t name_fd,
+		uint32_t plane_idx, uint32_t offset, uint32_t stride,
+		uint32_t modifier_hi, uint32_t modifier_lo) {
+	struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_params_resource(
+		params_resource);
+
+	if (!buffer) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
+			"params was already used to create a wl_buffer");
+		close(name_fd);
+		return;
+	}
+
+	if (plane_idx >= WLR_LINUX_DMABUF_MAX_PLANES) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
+			"plane index %u > %u", plane_idx, WLR_LINUX_DMABUF_MAX_PLANES);
+		close(name_fd);
+		return;
+	}
+
+	if (buffer->attributes.fd[plane_idx] != -1) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
+			"a dmabuf with id %d has already been added for plane %u",
+			buffer->attributes.fd[plane_idx],
+			plane_idx);
+		close(name_fd);
+		return;
+	}
+
+	buffer->attributes.fd[plane_idx] = name_fd;
+	buffer->attributes.offset[plane_idx] = offset;
+	buffer->attributes.stride[plane_idx] = stride;
+	buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) |
+		modifier_lo;
+	buffer->attributes.n_planes++;
+}
+
+static void handle_buffer_destroy(struct wl_resource *buffer_resource)
+{
+	struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_buffer_resource(
+		buffer_resource);
+
+	linux_dmabuf_buffer_destroy(buffer);
+}
+
+static void params_create_common(struct wl_client *client,
+		struct wl_resource *params_resource, uint32_t buffer_id, int32_t width,
+		int32_t height, uint32_t format, uint32_t flags) {
+	if (!wl_resource_get_user_data(params_resource)) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
+			"params was already used to create a wl_buffer");
+		return;
+	}
+	struct wlr_dmabuf_buffer *buffer = wlr_dmabuf_buffer_from_params_resource(
+		params_resource);
+
+	/* Switch the linux_dmabuf_buffer object from params resource to
+	 * eventually wl_buffer resource. */
+	wl_resource_set_user_data(buffer->params_resource, NULL);
+	buffer->params_resource = NULL;
+
+	if (!buffer->attributes.n_planes) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
+			"no dmabuf has been added to the params");
+		goto err_out;
+	}
+
+	/* TODO: support more planes */
+	if (buffer->attributes.n_planes != 1) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
+			"only single plane buffers supported not %d",
+			buffer->attributes.n_planes);
+		goto err_out;
+	}
+
+	if (buffer->attributes.fd[0] == -1) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
+			"no dmabuf has been added for plane");
+		goto err_out;
+	}
+
+	buffer->attributes.width = width;
+	buffer->attributes.height = height;
+	buffer->attributes.format = format;
+	buffer->attributes.flags = flags;
+
+	if (width < 1 || height < 1) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
+			"invalid width %d or height %d", width, height);
+		goto err_out;
+	}
+
+	if ((uint64_t)buffer->attributes.offset[0] + buffer->attributes.stride[0] > UINT32_MAX) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+			"size overflow for plane");
+		goto err_out;
+	}
+
+	if ((uint64_t)buffer->attributes.offset[0] +
+			(uint64_t)buffer->attributes.stride[0] * height > UINT32_MAX) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+			"size overflow for plane");
+		goto err_out;
+	}
+
+	off_t size = lseek(buffer->attributes.fd[0], 0, SEEK_END);
+	if (size != -1) { /* Skip checks if kernel does no support seek on buffer */
+		if (buffer->attributes.offset[0] >= size) {
+			wl_resource_post_error(params_resource,
+				ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+				"invalid offset %i for plane",
+				buffer->attributes.offset[0]);
+			goto err_out;
+		}
+
+		if (buffer->attributes.offset[0] + buffer->attributes.stride[0] > size) {
+			wl_resource_post_error(params_resource,
+				ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+				"invalid stride %i for plane",
+				buffer->attributes.stride[0]);
+			goto err_out;
+		}
+
+		if (buffer->attributes.offset[0] + buffer->attributes.stride[0] * height > size) {
+			wl_resource_post_error(params_resource,
+				ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+				"invalid buffer stride or height for plane");
+			goto err_out;
+		}
+	}
+
+	/* reject unknown flags */
+	if (buffer->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) {
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT,
+			"Unknown dmabuf flags %"PRIu32, buffer->attributes.flags);
+		goto err_out;
+	}
+
+	/* Check if dmabuf is usable */
+	if (!wlr_egl_check_import_dmabuf(buffer->egl, buffer)) {
+		goto err_failed;
+	}
+
+	buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface,
+		1, buffer_id);
+	if (!buffer->buffer_resource) {
+		wl_resource_post_no_memory(params_resource);
+		goto err_failed;
+	}
+
+	wl_resource_set_implementation(buffer->buffer_resource,
+		&wl_buffer_impl, buffer, handle_buffer_destroy);
+
+	/* send 'created' event when the request is not for an immediate
+	 * import, that is buffer_id is zero */
+	if (buffer_id == 0) {
+		zwp_linux_buffer_params_v1_send_created(params_resource,
+			buffer->buffer_resource);
+	}
+	return;
+
+err_failed:
+	if (buffer_id == 0) {
+		zwp_linux_buffer_params_v1_send_failed(params_resource);
+	} else {
+		/* since the behavior is left implementation defined by the
+		 * protocol in case of create_immed failure due to an unknown cause,
+		 * we choose to treat it as a fatal error and immediately kill the
+		 * client instead of creating an invalid handle and waiting for it
+		 * to be used.
+		 */
+		wl_resource_post_error(params_resource,
+			ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
+			"importing the supplied dmabufs failed");
+	}
+err_out:
+	linux_dmabuf_buffer_destroy(buffer);
+	return;
+}
+
+static void params_create(struct wl_client *client,
+		struct wl_resource *params_resource,
+		int32_t width, int32_t height,uint32_t format, uint32_t flags) {
+	params_create_common(client, params_resource, 0, width, height, format, flags);
+}
+
+static void params_create_immed(struct wl_client *client,
+		struct wl_resource *params_resource, uint32_t buffer_id,
+		int32_t width, int32_t height,uint32_t format, uint32_t flags) {
+	params_create_common(client, params_resource, buffer_id, width, height, format, flags);
+}
+
+static const struct zwp_linux_buffer_params_v1_interface linux_buffer_params_impl = {
+	params_destroy,
+	params_add,
+	params_create,
+	params_create_immed,
+};
+
+struct wlr_dmabuf_buffer *wlr_dmabuf_buffer_from_params_resource(
+		struct wl_resource *params_resource) {
+	assert(wl_resource_instance_of(params_resource,
+		&zwp_linux_buffer_params_v1_interface,
+		&linux_buffer_params_impl));
+
+	struct wlr_dmabuf_buffer *buffer = wl_resource_get_user_data(params_resource);
+	assert(buffer);
+	assert(buffer->params_resource);
+	assert(!buffer->buffer_resource);
+	assert(buffer->params_resource == params_resource);
+
+	return buffer;
+}
+
+static void handle_params_destroy(struct wl_resource *params_resource) {
+	/* Check for NULL since wlr_dmabuf_buffer_from_params_resource will choke */
+	if (!wl_resource_get_user_data(params_resource)) {
+		return;
+	}
+
+	struct wlr_dmabuf_buffer *buffer =
+		wlr_dmabuf_buffer_from_params_resource(params_resource);
+	linux_dmabuf_buffer_destroy(buffer);
+}
+
+static void linux_dmabuf_create_params(struct wl_client *client,
+		struct wl_resource *linux_dmabuf_resource,
+		uint32_t params_id) {
+	struct wlr_linux_dmabuf *linux_dmabuf = wlr_linux_dmabuf_from_resource(
+		linux_dmabuf_resource);
+
+	uint32_t version = wl_resource_get_version(linux_dmabuf_resource);
+	struct wlr_dmabuf_buffer *buffer = calloc(1, sizeof *buffer);
+	if (!buffer) {
+		goto err;
+	}
+
+	for (int i = 0; i < WLR_LINUX_DMABUF_MAX_PLANES; i++) {
+		buffer->attributes.fd[i] = -1;
+	}
+
+	buffer->egl = linux_dmabuf->egl;
+	buffer->params_resource = wl_resource_create(client,
+		&zwp_linux_buffer_params_v1_interface,
+		version, params_id);
+	if (!buffer->params_resource) {
+		goto err_free;
+	}
+
+	wl_resource_set_implementation(buffer->params_resource,
+		&linux_buffer_params_impl,buffer, handle_params_destroy);
+	return;
+
+err_free:
+	free(buffer);
+err:
+	wl_resource_post_no_memory(linux_dmabuf_resource);
+}
+
+static void linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) {
+	wl_resource_destroy(resource);
+}
+
+static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = {
+	linux_dmabuf_destroy,
+	linux_dmabuf_create_params
+};
+
+struct wlr_linux_dmabuf *wlr_linux_dmabuf_from_resource(
+		struct wl_resource *resource) {
+	assert(wl_resource_instance_of(resource, &zwp_linux_dmabuf_v1_interface,
+			&linux_dmabuf_impl));
+
+	struct wlr_linux_dmabuf *dmabuf = wl_resource_get_user_data(resource);
+	assert(dmabuf);
+	return dmabuf;
+}
+
+static void linux_dmabuf_send_modifiers(struct wlr_linux_dmabuf *linux_dmabuf,
+		struct wl_resource *resource) {
+	struct wlr_egl *egl = linux_dmabuf->egl;
+	/*
+	 * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise
+	 * format/modifier codes.
+	 */
+	uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID;
+	int *formats = NULL;
+	int num_formats = wlr_egl_get_dmabuf_formats(egl, &formats);
+
+	if (num_formats < 0) {
+		return;
+	}
+
+	for (int i = 0; i < num_formats; i++) {
+		int num_modifiers;
+		uint64_t *modifiers = NULL;
+
+		num_modifiers = wlr_egl_get_dmabuf_modifiers(egl, formats[i],
+			&modifiers);
+		if (num_modifiers < 0) {
+			return;
+		}
+		/* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported
+		 * for this format */
+		if (num_modifiers == 0) {
+			num_modifiers = 1;
+			modifiers = &modifier_invalid;
+		}
+		for (int j = 0; j < num_modifiers; j++) {
+			uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF;
+			uint32_t modifier_hi = modifiers[j] >> 32;
+			zwp_linux_dmabuf_v1_send_modifier(resource, formats[i],
+				modifier_hi,
+				modifier_lo);
+		}
+		if (modifiers != &modifier_invalid) {
+			free(modifiers);
+		}
+	}
+	free(formats);
+}
+
+static void linux_dmabuf_bind(struct wl_client *client,
+		void *data, uint32_t version, uint32_t id) {
+	struct wlr_linux_dmabuf *linux_dmabuf = data;
+	struct wl_resource *resource = wl_resource_create(client,
+		  &zwp_linux_dmabuf_v1_interface,
+		  version, id);
+
+	if (resource == NULL) {
+		wl_client_post_no_memory(client);
+		return;
+	}
+
+	wl_resource_set_implementation(resource, &linux_dmabuf_impl,
+		linux_dmabuf, NULL);
+	if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
+                return;
+	}
+
+	linux_dmabuf_send_modifiers(linux_dmabuf, resource);
+}
+
+void wlr_linux_dmabuf_destroy(struct wlr_linux_dmabuf *linux_dmabuf) {
+	if (!linux_dmabuf) {
+		return;
+	}
+	wl_list_remove(&linux_dmabuf->display_destroy.link);
+
+	wl_global_destroy(linux_dmabuf->wl_global);
+	free(linux_dmabuf);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+	struct wlr_linux_dmabuf *linux_dmabuf = wl_container_of(listener, linux_dmabuf, display_destroy);
+	wlr_linux_dmabuf_destroy(linux_dmabuf);
+}
+
+struct wlr_linux_dmabuf *wlr_linux_dmabuf_create(struct wl_display *display,
+		struct wlr_egl *egl) {
+	struct wlr_linux_dmabuf *linux_dmabuf =
+		calloc(1, sizeof(struct wlr_linux_dmabuf));
+	if (linux_dmabuf == NULL) {
+		wlr_log(L_ERROR, "could not create simple dmabuf manager");
+		return NULL;
+	}
+
+	linux_dmabuf->display_destroy.notify = handle_display_destroy;
+	wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy);
+
+	linux_dmabuf->wl_global =
+		wl_global_create(display, &zwp_linux_dmabuf_v1_interface,
+			3, linux_dmabuf, linux_dmabuf_bind);
+
+	linux_dmabuf->egl = egl;
+	if (!linux_dmabuf->wl_global) {
+		wlr_log(L_ERROR, "could not create linux dmabuf v1 wl global");
+		free(linux_dmabuf);
+		return NULL;
+	}
+
+	return linux_dmabuf;
+}
diff --git a/types/wlr_surface.c b/types/wlr_surface.c
index ecab4842..cf00a1e6 100644
--- a/types/wlr_surface.c
+++ b/types/wlr_surface.c
@@ -325,6 +325,10 @@ static void wlr_surface_apply_damage(struct wlr_surface *surface,
 					surface->current->buffer)) {
 			wlr_texture_upload_drm(surface->texture, surface->current->buffer);
 			goto release;
+		} else if (wlr_dmabuf_resource_is_buffer(
+					   surface->current->buffer)) {
+			wlr_texture_upload_dmabuf(surface->texture, surface->current->buffer);
+			goto release;
 		} else {
 			wlr_log(L_INFO, "Unknown buffer handle attached");
 			return;