diff --git a/tests/testdisplay.c b/tests/testdisplay.c
index df3b4d4..a8b39fe 100644
--- a/tests/testdisplay.c
+++ b/tests/testdisplay.c
@@ -54,10 +54,13 @@
 #include <math.h>
 #include <stdint.h>
 #include <stdbool.h>
+#include <strings.h>
 #include <unistd.h>
 #include <sys/poll.h>
 #include <sys/time.h>
 #include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #include "i915_drm.h"
 #include "drmtest.h"
@@ -69,7 +72,7 @@
 drmModeRes *resources;
 int drm_fd, modes;
 int test_all_modes = 0, test_preferred_mode = 0, force_mode = 0, test_plane,
-    enable_tiling;
+    test_3d_modes, enable_tiling;
 int sleep_between_modes = 5;
 uint32_t depth = 24, stride, bpp;
 int qr_code = 0;
@@ -362,6 +365,9 @@ set_mode(struct connector *c)
 		if (test_all_modes)
 			c->mode = c->connector->modes[j];
 
+		/* set_mode() only tests 2D modes */
+		c->mode.flags &= ~DRMTEST_MODE_FLAG_3D_MASK;
+
 		if (!c->mode_valid)
 			continue;
 
@@ -404,6 +410,223 @@ set_mode(struct connector *c)
 	drmModeFreeConnector(c->connector);
 }
 
+static void adjust_3d_timings(drmModeModeInfo *mode, unsigned int format)
+{
+	uint16_t vdisplay, vactive_space;
+
+	/* just set the 3D format we are setting (this is not used by the
+	 * kernel, it's just for kmstest_dump_mode()) */
+	mode->flags &= ~DRMTEST_MODE_FLAG_3D_MASK;
+	mode->flags |= format;
+
+	switch (format) {
+	case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+	case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+		return;
+	case DRM_MODE_FLAG_3D_FRAME_PACKING:
+		vactive_space = mode->vtotal - mode->vdisplay;
+		vdisplay = mode->vdisplay;
+
+		mode->vdisplay += vdisplay + vactive_space;
+		mode->vsync_start += vdisplay + vactive_space;
+		mode->vsync_end += vdisplay + vactive_space;
+		mode->vtotal += vdisplay + vactive_space;
+		mode->clock = (mode->vtotal * mode->htotal * mode->vrefresh) /
+			      1000;
+		return;
+	default:
+		assert(0);
+	}
+}
+
+struct box {
+	int x, y, width, height;
+};
+
+struct s3d_fb_layout {
+	int fb_width, fb_height;
+	struct box left, right;
+};
+
+static void box_init(struct box *box, int x, int y, int bwidth, int bheight)
+{
+	box->x = x;
+	box->y = y;
+	box->width = bwidth;
+	box->height = bheight;
+}
+
+static void box_print(const char * prefix, struct box *box)
+{
+	printf("%s: %d, %d, %d, %d\n", prefix,
+			box->x, box->y, box->width, box->height);
+}
+
+static void s3d_fb_layout_from_mode(struct s3d_fb_layout *layout,
+				    drmModeModeInfo *mode)
+{
+	unsigned int format = mode->flags & DRMTEST_MODE_FLAG_3D_MASK;
+	const int hdisplay = mode->hdisplay, vdisplay = mode->vdisplay;
+	int middle;
+
+	switch (format) {
+	case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+		layout->fb_width = hdisplay;
+		layout->fb_height = vdisplay;
+
+		middle = vdisplay / 2;
+		box_init(&layout->left, 0, 0, hdisplay, middle);
+		box_init(&layout->right,
+			 0, middle, hdisplay, vdisplay - middle);
+		break;
+	case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+		layout->fb_width = hdisplay;
+		layout->fb_height = vdisplay;
+
+		middle = hdisplay / 2;
+		box_init(&layout->left, 0, 0, middle, vdisplay);
+		box_init(&layout->right,
+			 middle, 0, hdisplay - middle, vdisplay);
+		break;
+	case DRM_MODE_FLAG_3D_FRAME_PACKING:
+	{
+		int vactive_space = mode->vtotal - vdisplay;
+
+		layout->fb_width = hdisplay;
+		layout->fb_height = 2 * vdisplay + vactive_space;
+
+		box_init(&layout->left,
+			 0, 0, hdisplay, vdisplay);
+		box_init(&layout->right,
+			 0, vdisplay + vactive_space, hdisplay, vdisplay);
+		break;
+	}
+	default:
+		assert(0);
+	}
+}
+
+static const char *s3d_mode_str(unsigned format)
+{
+	switch (format) {
+	case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+		return "TB";
+	case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+		return "SbSH";
+	case DRM_MODE_FLAG_3D_FRAME_PACKING:
+		return "FP";
+	default:
+		assert(0);
+	}
+}
+
+static uint32_t create_s3d_fb(drmModeModeInfo *mode, struct kmstest_fb *fb)
+{
+	struct s3d_fb_layout layout;
+	cairo_t *cr;
+	uint32_t fb_id;
+
+	s3d_fb_layout_from_mode(&layout, mode);
+	box_print("left: ", &layout.left);
+	box_print("right: ", &layout.right);
+	fb_id = kmstest_create_fb(drm_fd, layout.fb_width, layout.fb_height,
+				  bpp, depth, enable_tiling, fb);
+	cr = kmstest_get_cairo_ctx(drm_fd, fb);
+
+	kmstest_paint_image(cr, IGT_DATADIR"/1080p-left.png",
+			    layout.left.x, layout.left.y,
+			    layout.left.width, layout.left.height);
+	kmstest_paint_image(cr, IGT_DATADIR"/1080p-right.png",
+			    layout.right.x, layout.right.y,
+			    layout.right.width, layout.right.height);
+
+#if 0 /* Comment this out if you want to see what the composited fb looks
+	 like */
+	{
+		char buffer[64];
+
+		snprintf(buffer, sizeof(buffer), "%dx%d@%dHz-%s.png",
+			 mode->hdisplay,
+			 mode->vdisplay,
+			 mode->vrefresh,
+			 s3d_mode_str(mode->flags & DRMTEST_MODE_FLAG_3D_MASK));
+
+		kmstest_write_fb(drm_fd, fb, buffer);
+	}
+#endif
+
+	return fb_id;
+}
+
+static void do_set_3d_format(struct connector *c, unsigned int format)
+{
+	uint32_t fb_id;
+	struct kmstest_fb fb_info;
+
+	fb_id = create_s3d_fb(&c->mode, &fb_info);
+	adjust_3d_timings(&c->mode, format);
+
+	if (drmModeSetCrtc(drm_fd, c->crtc, fb_id, 0, 0,
+			   &c->id, 1, &c->mode)) {
+		fprintf(stderr, "failed to set mode (%dx%d@%dHz): %s\n",
+			width, height, c->mode.vrefresh,
+			strerror(errno));
+	}
+}
+
+static void
+set_3d_mode(struct connector *c)
+{
+	int i;
+
+	for (i = 0; i < c->connector->count_modes; i++) {
+		unsigned int s3d_formats, format;
+
+		c->mode = c->connector->modes[i];
+
+		if (!c->mode_valid)
+			continue;
+
+		s3d_formats = c->mode.flags & DRMTEST_MODE_FLAG_3D_MASK;
+		if (!s3d_formats)
+			continue;
+
+		do {
+			format = 1 << (ffs(s3d_formats) - 1);
+
+			/*
+			 * Modify the mode flags to specify which 3D format is
+			 * being set.
+			 *
+			 * XXX: One would need to also clear the upper bits of
+			 * flags in case extra modes/flags are added
+			 */
+			c->mode.flags &= ~DRMTEST_MODE_FLAG_3D_MASK;
+			c->mode.flags |= format;
+
+			do_set_3d_format(c, format);
+
+			/*
+			 * The mode may have been adjusted for this format,
+			 * reset it to its origin timings
+			 */
+			c->mode = c->connector->modes[i];
+
+			if (qr_code){
+				set_single();
+				pause();
+			} else if (sleep_between_modes)
+				sleep(sleep_between_modes);
+
+			s3d_formats &= ~format;
+		} while (s3d_formats);
+
+	}
+
+	drmModeFreeEncoder(c->encoder);
+	drmModeFreeConnector(c->connector);
+}
+
 /*
  * Re-probe outputs and light up as many as possible.
  *
@@ -458,12 +681,29 @@ int update_display(void)
 		}
 	}
 
+	if (test_3d_modes) {
+		for (c = 0; c < resources->count_connectors; c++) {
+			struct connector *connector = &connectors[c];
+
+			connector->id = resources->connectors[c];
+
+			connector_find_preferred_mode(connector->id,
+						      -1UL,
+						      specified_mode_num,
+						      connector);
+			if (!connector->mode_valid)
+				continue;
+
+			set_3d_mode(connector);
+		}
+	}
+
 	free(connectors);
 	drmModeFreeResources(resources);
 	return 1;
 }
 
-static char optstr[] = "hiaf:s:d:p:mrto:";
+static char optstr[] = "3hiaf:s:d:p:mrto:";
 
 static void __attribute__((noreturn)) usage(char *name)
 {
@@ -474,6 +714,7 @@ static void __attribute__((noreturn)) usage(char *name)
 	fprintf(stderr, "\t-d\t<depth>\tbit depth of scanout buffer\n");
 	fprintf(stderr, "\t-p\t<planew,h>,<crtcx,y>,<crtcw,h> test overlay plane\n");
 	fprintf(stderr, "\t-m\ttest the preferred mode\n");
+	fprintf(stderr, "\t-3\ttest all 3D modes\n");
 	fprintf(stderr, "\t-t\tuse a tiled framebuffer\n");
 	fprintf(stderr, "\t-r\tprint a QR code on the screen whose content is \"pass\" for the automatic test\n");
 	fprintf(stderr, "\t-o\t<id of the display>,<number of the mode>\tonly test specified mode on the specified display\n");
@@ -536,6 +777,9 @@ int main(int argc, char **argv)
 	opterr = 0;
 	while ((c = getopt(argc, argv, optstr)) != -1) {
 		switch (c) {
+		case '3':
+			test_3d_modes = 1;
+			break;
 		case 'i':
 			opt_dump_info = true;
 			break;
@@ -594,11 +838,16 @@ int main(int argc, char **argv)
 		bpp = 32;
 
 	if (!test_all_modes && !force_mode && !test_preferred_mode &&
-	    specified_mode_num == -1)
+	    specified_mode_num == -1 && !test_3d_modes)
 		test_all_modes = 1;
 
 	drm_fd = drm_open_any();
 
+	if (test_3d_modes && drmSetCap(drm_fd, DRM_CAP_STEREO_3D, 1) < 0) {
+		fprintf(stderr, "DRM_CAP_STEREO_3D isn't supported\n");
+		goto out_close;
+	}
+
 	if (opt_dump_info) {
 		dump_info();
 		goto out_close;
