compositor-drm: Fully account for buffer transformation
[platform/upstream/weston.git] / libweston / compositor-drm.c
index d192cf3..787ffad 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * Copyright © 2008-2011 Kristian Høgsberg
  * Copyright © 2011 Intel Corporation
+ * Copyright © 2017, 2018 Collabora, Ltd.
+ * Copyright © 2017, 2018 General Electric Company
  *
  * Permission is hereby granted, free of charge, to any person obtaining
  * a copy of this software and associated documentation files (the
 #define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2
 #endif
 
+#ifndef DRM_CLIENT_CAP_ASPECT_RATIO
+#define DRM_CLIENT_CAP_ASPECT_RATIO    4
+#endif
+
 #ifndef DRM_CAP_CURSOR_WIDTH
 #define DRM_CAP_CURSOR_WIDTH 0x8
 #endif
 #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64
 #endif
 
+#define MAX_CLONED_CONNECTORS 4
+
+/**
+ * aspect ratio info taken from the drmModeModeInfo flag bits 19-22,
+ * which should be used to fill the aspect ratio field in weston_mode.
+ */
+#define DRM_MODE_FLAG_PIC_AR_BITS_POS  19
+#ifndef DRM_MODE_FLAG_PIC_AR_MASK
+#define DRM_MODE_FLAG_PIC_AR_MASK (0xF << DRM_MODE_FLAG_PIC_AR_BITS_POS)
+#endif
+
 /**
  * Represents the values of an enum-type KMS property
  */
@@ -241,7 +258,6 @@ struct drm_backend {
         */
        int min_width, max_width;
        int min_height, max_height;
-       int no_addfb2;
 
        struct wl_list plane_list;
        int sprites_are_broken;
@@ -251,8 +267,7 @@ struct drm_backend {
 
        bool state_invalid;
 
-       /* Connector and CRTC IDs not used by any enabled output. */
-       struct wl_array unused_connectors;
+       /* CRTC IDs not used by any enabled output. */
        struct wl_array unused_crtcs;
 
        int cursors_are_broken;
@@ -261,6 +276,7 @@ struct drm_backend {
        bool atomic_modeset;
 
        int use_pixman;
+       bool use_pixman_shadow;
 
        struct udev_input input;
 
@@ -270,6 +286,8 @@ struct drm_backend {
        uint32_t pageflip_timeout;
 
        bool shutting_down;
+
+       bool aspect_ratio_supported;
 };
 
 struct drm_mode {
@@ -401,22 +419,32 @@ struct drm_plane {
        uint32_t formats[];
 };
 
-struct drm_output {
-       struct weston_output base;
-       drmModeConnector *connector;
+struct drm_head {
+       struct weston_head base;
+       struct drm_backend *backend;
 
-       uint32_t crtc_id; /* object ID to pass to DRM functions */
-       int pipe; /* index of CRTC in resource array / bitmasks */
+       drmModeConnector *connector;
        uint32_t connector_id;
        struct drm_edid edid;
 
        /* Holds the properties for the connector */
        struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT];
-       /* Holds the properties for the CRTC */
-       struct drm_property_info props_crtc[WDRM_CRTC__COUNT];
 
        struct backlight *backlight;
 
+       drmModeModeInfo inherited_mode; /**< Original mode on the connector */
+       uint32_t inherited_crtc_id;     /**< Original CRTC assignment */
+};
+
+struct drm_output {
+       struct weston_output base;
+
+       uint32_t crtc_id; /* object ID to pass to DRM functions */
+       int pipe; /* index of CRTC in resource array / bitmasks */
+
+       /* Holds the properties for the CRTC */
+       struct drm_property_info props_crtc[WDRM_CRTC__COUNT];
+
        int vblank_pending;
        int page_flip_pending;
        int atomic_complete_pending;
@@ -452,6 +480,14 @@ struct drm_output {
        struct wl_event_source *pageflip_timer;
 };
 
+static const char *const aspect_ratio_as_string[] = {
+       [WESTON_MODE_PIC_AR_NONE] = "",
+       [WESTON_MODE_PIC_AR_4_3] = " 4:3",
+       [WESTON_MODE_PIC_AR_16_9] = " 16:9",
+       [WESTON_MODE_PIC_AR_64_27] = " 64:27",
+       [WESTON_MODE_PIC_AR_256_135] = " 256:135",
+};
+
 static struct gl_renderer_interface *gl_renderer;
 
 static const char default_seat[] = "seat0";
@@ -476,6 +512,12 @@ wl_array_remove_uint32(struct wl_array *array, uint32_t elm)
        }
 }
 
+static inline struct drm_head *
+to_drm_head(struct weston_head *base)
+{
+       return container_of(base, struct drm_head, base);
+}
+
 static inline struct drm_output *
 to_drm_output(struct weston_output *base)
 {
@@ -549,7 +591,7 @@ to_drm_mode(struct weston_mode *base)
  */
 static uint64_t
 drm_property_get_value(struct drm_property_info *info,
-                      drmModeObjectPropertiesPtr props,
+                      const drmModeObjectProperties *props,
                       uint64_t def)
 {
        unsigned int i;
@@ -721,7 +763,9 @@ drm_property_info_populate(struct drm_backend *b,
 /**
  * Free DRM property information
  *
- * Frees all memory associated with a DRM property info array.
+ * Frees all memory associated with a DRM property info array and zeroes
+ * it out, leaving it usable for a further drm_property_info_update() or
+ * drm_property_info_free().
  *
  * @param info DRM property info array
  * @param num_props Number of entries in array to free
@@ -733,6 +777,8 @@ drm_property_info_free(struct drm_property_info *info, int num_props)
 
        for (i = 0; i < num_props; i++)
                free(info[i].enum_values);
+
+       memset(info, 0, sizeof(*info) * num_props);
 }
 
 static void
@@ -776,29 +822,20 @@ drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id)
                        return output;
        }
 
-       wl_list_for_each(output, &b->compositor->pending_output_list,
-                        base.link) {
-               if (output->crtc_id == crtc_id)
-                       return output;
-       }
-
        return NULL;
 }
 
-static struct drm_output *
-drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id)
+static struct drm_head *
+drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id)
 {
-       struct drm_output *output;
+       struct weston_head *base;
+       struct drm_head *head;
 
-       wl_list_for_each(output, &b->compositor->output_list, base.link) {
-               if (output->connector_id == connector_id)
-                       return output;
-       }
-
-       wl_list_for_each(output, &b->compositor->pending_output_list,
-                        base.link) {
-               if (output->connector_id == connector_id)
-                       return output;
+       wl_list_for_each(base,
+                        &backend->compositor->head_list, compositor_link) {
+               head = to_drm_head(base);
+               if (head->connector_id == connector_id)
+                       return head;
        }
 
        return NULL;
@@ -850,6 +887,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height,
        struct drm_mode_create_dumb create_arg;
        struct drm_mode_destroy_dumb destroy_arg;
        struct drm_mode_map_dumb map_arg;
+       uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 };
 
        fb = zalloc(sizeof *fb);
        if (!fb)
@@ -887,25 +925,12 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height,
        fb->height = height;
        fb->fd = b->drm.fd;
 
-       ret = -1;
-
-       if (!b->no_addfb2) {
-               uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 };
-
-               handles[0] = fb->handle;
-               pitches[0] = fb->stride;
-               offsets[0] = 0;
-
-               ret = drmModeAddFB2(b->drm.fd, width, height,
-                                   fb->format->format,
-                                   handles, pitches, offsets,
-                                   &fb->fb_id, 0);
-               if (ret) {
-                       weston_log("addfb2 failed: %m\n");
-                       b->no_addfb2 = 1;
-               }
-       }
+       handles[0] = fb->handle;
+       pitches[0] = fb->stride;
+       offsets[0] = 0;
 
+       ret = drmModeAddFB2(b->drm.fd, width, height, fb->format->format,
+                           handles, pitches, offsets, &fb->fb_id, 0);
        if (ret) {
                ret = drmModeAddFB(b->drm.fd, width, height,
                                   fb->format->depth, fb->format->bpp,
@@ -948,7 +973,7 @@ drm_fb_ref(struct drm_fb *fb)
 
 static struct drm_fb *
 drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend,
-                  uint32_t format, enum drm_fb_type type)
+                  bool is_opaque, enum drm_fb_type type)
 {
        struct drm_fb *fb = gbm_bo_get_user_data(bo);
        uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 };
@@ -971,16 +996,21 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend,
        fb->height = gbm_bo_get_height(bo);
        fb->stride = gbm_bo_get_stride(bo);
        fb->handle = gbm_bo_get_handle(bo).u32;
-       fb->format = pixel_format_get_info(format);
+       fb->format = pixel_format_get_info(gbm_bo_get_format(bo));
        fb->size = fb->stride * fb->height;
        fb->fd = backend->drm.fd;
 
        if (!fb->format) {
                weston_log("couldn't look up format 0x%lx\n",
-                          (unsigned long) format);
+                          (unsigned long) gbm_bo_get_format(bo));
                goto err_free;
        }
 
+       /* We can scanout an ARGB buffer if the surface's opaque region covers
+        * the whole output, but we have to use XRGB as the KMS format code. */
+       if (is_opaque)
+               fb->format = pixel_format_get_opaque_substitute(fb->format);
+
        if (backend->min_width > fb->width ||
            fb->width > backend->max_width ||
            backend->min_height > fb->height ||
@@ -989,23 +1019,13 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend,
                goto err_free;
        }
 
-       ret = -1;
-
-       if (format && !backend->no_addfb2) {
-               handles[0] = fb->handle;
-               pitches[0] = fb->stride;
-               offsets[0] = 0;
-
-               ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height,
-                                   format, handles, pitches, offsets,
-                                   &fb->fb_id, 0);
-               if (ret) {
-                       weston_log("addfb2 failed: %m\n");
-                       backend->no_addfb2 = 1;
-                       backend->sprites_are_broken = 1;
-               }
-       }
+       handles[0] = fb->handle;
+       pitches[0] = fb->stride;
+       offsets[0] = 0;
 
+       ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height,
+                           fb->format->format, handles, pitches, offsets,
+                           &fb->fb_id, 0);
        if (ret && fb->format->depth && fb->format->bpp)
                ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height,
                                   fb->format->depth, fb->format->bpp,
@@ -1178,6 +1198,90 @@ drm_plane_state_put_back(struct drm_plane_state *state)
 }
 
 /**
+ * Given a weston_view, fill the drm_plane_state's co-ordinates to display on
+ * a given plane.
+ */
+static void
+drm_plane_state_coords_for_view(struct drm_plane_state *state,
+                               struct weston_view *ev)
+{
+       struct drm_output *output = state->output;
+       struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
+       pixman_region32_t dest_rect, src_rect;
+       pixman_box32_t *box, tbox;
+       float sxf1, syf1, sxf2, syf2;
+
+       /* Update the base weston_plane co-ordinates. */
+       box = pixman_region32_extents(&ev->transform.boundingbox);
+       state->plane->base.x = box->x1;
+       state->plane->base.y = box->y1;
+
+       /* First calculate the destination co-ordinates by taking the
+        * area of the view which is visible on this output, performing any
+        * transforms to account for output rotation and scale as necessary. */
+       pixman_region32_init(&dest_rect);
+       pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox,
+                                 &output->base.region);
+       pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y);
+       box = pixman_region32_extents(&dest_rect);
+       tbox = weston_transformed_rect(output->base.width,
+                                      output->base.height,
+                                      output->base.transform,
+                                      output->base.current_scale,
+                                      *box);
+       state->dest_x = tbox.x1;
+       state->dest_y = tbox.y1;
+       state->dest_w = tbox.x2 - tbox.x1;
+       state->dest_h = tbox.y2 - tbox.y1;
+       pixman_region32_fini(&dest_rect);
+
+       /* Now calculate the source rectangle, by finding the extents of the
+        * view, and working backwards to source co-ordinates. */
+       pixman_region32_init(&src_rect);
+       pixman_region32_intersect(&src_rect, &ev->transform.boundingbox,
+                                 &output->base.region);
+       box = pixman_region32_extents(&src_rect);
+       weston_view_from_global_float(ev, box->x1, box->y1, &sxf1, &syf1);
+       weston_surface_to_buffer_float(ev->surface, sxf1, syf1, &sxf1, &syf1);
+       weston_view_from_global_float(ev, box->x2, box->y2, &sxf2, &syf2);
+       weston_surface_to_buffer_float(ev->surface, sxf2, syf2, &sxf2, &syf2);
+       pixman_region32_fini(&src_rect);
+
+       /* Buffer transforms may mean that x2 is to the left of x1, and/or that
+        * y2 is above y1. */
+       if (sxf2 < sxf1) {
+               double tmp = sxf1;
+               sxf1 = sxf2;
+               sxf2 = tmp;
+       }
+       if (syf2 < syf1) {
+               double tmp = syf1;
+               syf1 = syf2;
+               syf2 = tmp;
+       }
+
+       /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */
+       state->src_x = wl_fixed_from_double(sxf1) << 8;
+       state->src_y = wl_fixed_from_double(syf1) << 8;
+       state->src_w = wl_fixed_from_double(sxf2 - sxf1) << 8;
+       state->src_h = wl_fixed_from_double(syf2 - syf1) << 8;
+
+       /* Clamp our source co-ordinates to surface bounds; it's possible
+        * for intermediate translations to give us slightly incorrect
+        * co-ordinates if we have, for example, multiple zooming
+        * transformations. View bounding boxes are also explicitly rounded
+        * greedily. */
+       if (state->src_x < 0)
+               state->src_x = 0;
+       if (state->src_y < 0)
+               state->src_y = 0;
+       if (state->src_w > (uint32_t) ((buffer->width << 16) - state->src_x))
+               state->src_w = (buffer->width << 16) - state->src_x;
+       if (state->src_h > (uint32_t) ((buffer->height << 16) - state->src_y))
+               state->src_h = (buffer->height << 16) - state->src_y;
+}
+
+/**
  * Return a plane state from a drm_output_state.
  */
 static struct drm_plane_state *
@@ -1521,34 +1625,23 @@ drm_view_transform_supported(struct weston_view *ev)
                (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE);
 }
 
-static uint32_t
-drm_output_check_scanout_format(struct drm_output *output,
-                               struct weston_surface *es, struct gbm_bo *bo)
+static bool
+drm_view_is_opaque(struct weston_view *ev)
 {
-       uint32_t format;
        pixman_region32_t r;
+       bool ret = false;
 
-       format = gbm_bo_get_format(bo);
-
-       if (format == GBM_FORMAT_ARGB8888) {
-               /* We can scanout an ARGB buffer if the surface's
-                * opaque region covers the whole output, but we have
-                * to use XRGB as the KMS format code. */
-               pixman_region32_init_rect(&r, 0, 0,
-                                         output->base.width,
-                                         output->base.height);
-               pixman_region32_subtract(&r, &r, &es->opaque);
-
-               if (!pixman_region32_not_empty(&r))
-                       format = GBM_FORMAT_XRGB8888;
+       pixman_region32_init_rect(&r, 0, 0,
+                                 ev->surface->width,
+                                 ev->surface->height);
+       pixman_region32_subtract(&r, &r, &ev->surface->opaque);
 
-               pixman_region32_fini(&r);
-       }
+       if (!pixman_region32_not_empty(&r))
+               ret = true;
 
-       if (output->gbm_format == format)
-               return format;
+       pixman_region32_fini(&r);
 
-       return 0;
+       return ret;
 }
 
 static struct weston_plane *
@@ -1562,7 +1655,6 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state,
        struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
        struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
        struct gbm_bo *bo;
-       uint32_t format;
 
        /* Don't import buffers which span multiple outputs. */
        if (ev->output_mask != (1u << output->base.id))
@@ -1615,17 +1707,19 @@ drm_output_prepare_scanout_view(struct drm_output_state *output_state,
        if (!bo)
                return NULL;
 
-       format = drm_output_check_scanout_format(output, ev->surface, bo);
-       if (format == 0) {
+       state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev),
+                                      BUFFER_CLIENT);
+       if (!state->fb) {
                drm_plane_state_put_back(state);
                gbm_bo_destroy(bo);
                return NULL;
        }
 
-       state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT);
-       if (!state->fb) {
+       /* Can't change formats with just a pageflip */
+       if (state->fb->format->format != output->gbm_format) {
+               /* No need to destroy the GBM BO here, as it's now owned
+                * by the FB. */
                drm_plane_state_put_back(state);
-               gbm_bo_destroy(bo);
                return NULL;
        }
 
@@ -1663,7 +1757,8 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage)
                return NULL;
        }
 
-       ret = drm_fb_get_from_bo(bo, b, output->gbm_format, BUFFER_GBM_SURFACE);
+       /* The renderer always produces an opaque image. */
+       ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE);
        if (!ret) {
                weston_log("failed to get drm_fb for bo\n");
                gbm_surface_release_buffer(output->gbm_surface, bo);
@@ -1680,25 +1775,17 @@ drm_output_render_pixman(struct drm_output_state *state,
 {
        struct drm_output *output = state->output;
        struct weston_compositor *ec = output->base.compositor;
-       pixman_region32_t total_damage, previous_damage;
-
-       pixman_region32_init(&total_damage);
-       pixman_region32_init(&previous_damage);
-
-       pixman_region32_copy(&previous_damage, damage);
-
-       pixman_region32_union(&total_damage, damage, &output->previous_damage);
-       pixman_region32_copy(&output->previous_damage, &previous_damage);
 
        output->current_image ^= 1;
 
        pixman_renderer_output_set_buffer(&output->base,
                                          output->image[output->current_image]);
+       pixman_renderer_output_set_hw_extra_damage(&output->base,
+                                                  &output->previous_damage);
 
-       ec->renderer->repaint_output(&output->base, &total_damage);
+       ec->renderer->repaint_output(&output->base, damage);
 
-       pixman_region32_fini(&total_damage);
-       pixman_region32_fini(&previous_damage);
+       pixman_region32_copy(&output->previous_damage, damage);
 
        return drm_fb_ref(output->dumb[output->current_image]);
 }
@@ -1809,23 +1896,32 @@ drm_output_apply_state_legacy(struct drm_output_state *state)
        struct drm_output *output = state->output;
        struct drm_backend *backend = to_drm_backend(output->base.compositor);
        struct drm_plane *scanout_plane = output->scanout_plane;
-       struct drm_property_info *dpms_prop =
-               &output->props_conn[WDRM_CONNECTOR_DPMS];
+       struct drm_property_info *dpms_prop;
        struct drm_plane_state *scanout_state;
        struct drm_plane_state *ps;
        struct drm_plane *p;
        struct drm_mode *mode;
+       struct drm_head *head;
+       uint32_t connectors[MAX_CLONED_CONNECTORS];
+       int n_conn = 0;
        struct timespec now;
        int ret = 0;
 
+       wl_list_for_each(head, &output->base.head_list, base.output_link) {
+               assert(n_conn < MAX_CLONED_CONNECTORS);
+               connectors[n_conn++] = head->connector_id;
+       }
+
        /* If disable_planes is set then assign_planes() wasn't
         * called for this render, so we could still have a stale
         * cursor plane set up.
         */
        if (output->base.disable_planes) {
                output->cursor_view = NULL;
-               output->cursor_plane->base.x = INT32_MIN;
-               output->cursor_plane->base.y = INT32_MIN;
+               if (output->cursor_plane) {
+                       output->cursor_plane->base.x = INT32_MIN;
+                       output->cursor_plane->base.y = INT32_MIN;
+               }
        }
 
        if (state->dpms != WESTON_DPMS_ON) {
@@ -1851,7 +1947,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state)
                }
 
                ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0,
-                                    &output->connector_id, 0, NULL);
+                                    NULL, 0, NULL);
                if (ret)
                        weston_log("drmModeSetCrtc failed disabling: %m\n");
 
@@ -1886,7 +1982,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state)
                ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id,
                                     scanout_state->fb->fb_id,
                                     0, 0,
-                                    &output->connector_id, 1,
+                                    connectors, n_conn,
                                     &mode->mode_info);
                if (ret) {
                        weston_log("set mode failed: %m\n");
@@ -1957,14 +2053,20 @@ drm_output_apply_state_legacy(struct drm_output_state *state)
                }
        }
 
-       if (dpms_prop->prop_id && state->dpms != output->state_cur->dpms) {
-               ret = drmModeConnectorSetProperty(backend->drm.fd,
-                                                 output->connector_id,
-                                                 dpms_prop->prop_id,
-                                                 state->dpms);
-               if (ret) {
-                       weston_log("DRM: DPMS: failed property set for %s\n",
-                                  output->base.name);
+       if (state->dpms != output->state_cur->dpms) {
+               wl_list_for_each(head, &output->base.head_list, base.output_link) {
+                       dpms_prop = &head->props_conn[WDRM_CONNECTOR_DPMS];
+                       if (dpms_prop->prop_id == 0)
+                               continue;
+
+                       ret = drmModeConnectorSetProperty(backend->drm.fd,
+                                                         head->connector_id,
+                                                         dpms_prop->prop_id,
+                                                         state->dpms);
+                       if (ret) {
+                               weston_log("DRM: DPMS: failed property set for %s\n",
+                                          head->base.name);
+                       }
                }
        }
 
@@ -1995,16 +2097,16 @@ crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output,
 }
 
 static int
-connector_add_prop(drmModeAtomicReq *req, struct drm_output *output,
+connector_add_prop(drmModeAtomicReq *req, struct drm_head *head,
                   enum wdrm_connector_property prop, uint64_t val)
 {
-       struct drm_property_info *info = &output->props_conn[prop];
+       struct drm_property_info *info = &head->props_conn[prop];
        int ret;
 
        if (info->prop_id == 0)
                return -1;
 
-       ret = drmModeAtomicAddProperty(req, output->connector_id,
+       ret = drmModeAtomicAddProperty(req, head->connector_id,
                                       info->prop_id, val);
        return (ret <= 0) ? -1 : 0;
 }
@@ -2051,6 +2153,7 @@ drm_output_apply_state_atomic(struct drm_output_state *state,
        struct drm_backend *backend = to_drm_backend(output->base.compositor);
        struct drm_plane_state *plane_state;
        struct drm_mode *current_mode = to_drm_mode(output->base.current_mode);
+       struct drm_head *head;
        int ret = 0;
 
        if (state->dpms != output->state_cur->dpms)
@@ -2064,13 +2167,17 @@ drm_output_apply_state_atomic(struct drm_output_state *state,
                ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID,
                                     current_mode->blob_id);
                ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1);
-               ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID,
-                                         output->crtc_id);
+
+               wl_list_for_each(head, &output->base.head_list, base.output_link) {
+                       ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID,
+                                                 output->crtc_id);
+               }
        } else {
                ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0);
                ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0);
-               ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID,
-                                         0);
+
+               wl_list_for_each(head, &output->base.head_list, base.output_link)
+                       ret |= connector_add_prop(req, head, WDRM_CONNECTOR_CRTC_ID, 0);
        }
 
        if (ret != 0) {
@@ -2130,47 +2237,36 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state,
                return -1;
 
        if (b->state_invalid) {
+               struct weston_head *head_base;
+               struct drm_head *head;
                uint32_t *unused;
                int err;
 
                /* If we need to reset all our state (e.g. because we've
                 * just started, or just been VT-switched in), explicitly
                 * disable all the CRTCs and connectors we aren't using. */
-               wl_array_for_each(unused, &b->unused_connectors) {
-                       struct drm_property_info infos[WDRM_CONNECTOR__COUNT];
+               wl_list_for_each(head_base,
+                                &b->compositor->head_list, compositor_link) {
                        struct drm_property_info *info;
-                       drmModeObjectProperties *props;
-
-                       memset(infos, 0, sizeof(infos));
 
-                       props = drmModeObjectGetProperties(b->drm.fd,
-                                                          *unused,
-                                                          DRM_MODE_OBJECT_CONNECTOR);
-                       if (!props) {
-                               ret = -1;
+                       if (weston_head_is_enabled(head_base))
                                continue;
-                       }
 
-                       drm_property_info_populate(b, connector_props, infos,
-                                                  WDRM_CONNECTOR__COUNT,
-                                                  props);
-                       drmModeFreeObjectProperties(props);
+                       head = to_drm_head(head_base);
 
-                       info = &infos[WDRM_CONNECTOR_CRTC_ID];
-                       err = drmModeAtomicAddProperty(req, *unused,
+                       info = &head->props_conn[WDRM_CONNECTOR_CRTC_ID];
+                       err = drmModeAtomicAddProperty(req, head->connector_id,
                                                       info->prop_id, 0);
                        if (err <= 0)
                                ret = -1;
 
-                       info = &infos[WDRM_CONNECTOR_DPMS];
+                       info = &head->props_conn[WDRM_CONNECTOR_DPMS];
                        if (info->prop_id > 0)
-                               err = drmModeAtomicAddProperty(req, *unused,
+                               err = drmModeAtomicAddProperty(req, head->connector_id,
                                                               info->prop_id,
                                                               DRM_MODE_DPMS_OFF);
                        if (err <= 0)
                                ret = -1;
-
-                       drm_property_info_free(infos, WDRM_CONNECTOR__COUNT);
                }
 
                wl_array_for_each(unused, &b->unused_crtcs) {
@@ -2635,52 +2731,20 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec,
 }
 #endif
 
-static uint32_t
-drm_output_check_plane_format(struct drm_plane *p,
-                              struct weston_view *ev, struct gbm_bo *bo)
-{
-       uint32_t i, format;
-
-       format = gbm_bo_get_format(bo);
-
-       if (format == GBM_FORMAT_ARGB8888) {
-               pixman_region32_t r;
-
-               pixman_region32_init_rect(&r, 0, 0,
-                                         ev->surface->width,
-                                         ev->surface->height);
-               pixman_region32_subtract(&r, &r, &ev->surface->opaque);
-
-               if (!pixman_region32_not_empty(&r))
-                       format = GBM_FORMAT_XRGB8888;
-
-               pixman_region32_fini(&r);
-       }
-
-       for (i = 0; i < p->count_formats; i++)
-               if (p->formats[i] == format)
-                       return format;
-
-       return 0;
-}
-
 static struct weston_plane *
 drm_output_prepare_overlay_view(struct drm_output_state *output_state,
                                struct weston_view *ev)
 {
        struct drm_output *output = output_state->output;
        struct weston_compositor *ec = output->base.compositor;
-       struct drm_backend *b = to_drm_backend(ec);
        struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
+       struct drm_backend *b = to_drm_backend(ec);
        struct wl_resource *buffer_resource;
        struct drm_plane *p;
        struct drm_plane_state *state = NULL;
        struct linux_dmabuf_buffer *dmabuf;
-       struct gbm_bo *bo;
-       pixman_region32_t dest_rect, src_rect;
-       pixman_box32_t *box, tbox;
-       uint32_t format;
-       wl_fixed_t sx1, sy1, sx2, sy2;
+       struct gbm_bo *bo = NULL;
+       unsigned int i;
 
        if (b->sprites_are_broken)
                return NULL;
@@ -2758,7 +2822,7 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state,
                if (dmabuf->attributes.n_planes != 1 ||
                     dmabuf->attributes.offset[0] != 0 ||
                    dmabuf->attributes.flags)
-                       return NULL;
+                       goto err;
 
                bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf,
                                   GBM_BO_USE_SCANOUT);
@@ -2772,89 +2836,32 @@ drm_output_prepare_overlay_view(struct drm_output_state *output_state,
        if (!bo)
                goto err;
 
-       format = drm_output_check_plane_format(p, ev, bo);
-       if (format == 0)
+       state->fb = drm_fb_get_from_bo(bo, b, drm_view_is_opaque(ev),
+                                      BUFFER_CLIENT);
+       if (!state->fb)
                goto err;
+       bo = NULL;
 
-       state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT);
-       if (!state->fb)
+       /* Check whether the format is supported */
+       for (i = 0; i < p->count_formats; i++)
+               if (p->formats[i] == state->fb->format->format)
+                       break;
+       if (i == p->count_formats)
                goto err;
 
        drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer);
 
        state->output = output;
-
-       box = pixman_region32_extents(&ev->transform.boundingbox);
-       p->base.x = box->x1;
-       p->base.y = box->y1;
-
-       /*
-        * Calculate the source & dest rects properly based on actual
-        * position (note the caller has called weston_view_update_transform()
-        * for us already).
-        */
-       pixman_region32_init(&dest_rect);
-       pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox,
-                                 &output->base.region);
-       pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y);
-       box = pixman_region32_extents(&dest_rect);
-       tbox = weston_transformed_rect(output->base.width,
-                                      output->base.height,
-                                      output->base.transform,
-                                      output->base.current_scale,
-                                      *box);
-       state->dest_x = tbox.x1;
-       state->dest_y = tbox.y1;
-       state->dest_w = tbox.x2 - tbox.x1;
-       state->dest_h = tbox.y2 - tbox.y1;
-       pixman_region32_fini(&dest_rect);
-
-       pixman_region32_init(&src_rect);
-       pixman_region32_intersect(&src_rect, &ev->transform.boundingbox,
-                                 &output->base.region);
-       box = pixman_region32_extents(&src_rect);
-
-       weston_view_from_global_fixed(ev,
-                                     wl_fixed_from_int(box->x1),
-                                     wl_fixed_from_int(box->y1),
-                                     &sx1, &sy1);
-       weston_view_from_global_fixed(ev,
-                                     wl_fixed_from_int(box->x2),
-                                     wl_fixed_from_int(box->y2),
-                                     &sx2, &sy2);
-
-       if (sx1 < 0)
-               sx1 = 0;
-       if (sy1 < 0)
-               sy1 = 0;
-       if (sx2 > wl_fixed_from_int(ev->surface->width))
-               sx2 = wl_fixed_from_int(ev->surface->width);
-       if (sy2 > wl_fixed_from_int(ev->surface->height))
-               sy2 = wl_fixed_from_int(ev->surface->height);
-
-       tbox.x1 = sx1;
-       tbox.y1 = sy1;
-       tbox.x2 = sx2;
-       tbox.y2 = sy2;
-
-       tbox = weston_transformed_rect(wl_fixed_from_int(ev->surface->width),
-                                      wl_fixed_from_int(ev->surface->height),
-                                      viewport->buffer.transform,
-                                      viewport->buffer.scale,
-                                      tbox);
-
-       state->src_x = tbox.x1 << 8;
-       state->src_y = tbox.y1 << 8;
-       state->src_w = (tbox.x2 - tbox.x1) << 8;
-       state->src_h = (tbox.y2 - tbox.y1) << 8;
-       pixman_region32_fini(&src_rect);
+       drm_plane_state_coords_for_view(state, ev);
 
        return &p->base;
 
 err:
-       drm_plane_state_put_back(state);
+       /* Destroy the BO as we've allocated it, but it won't yet
+        * be deallocated by the state. */
        if (bo)
                gbm_bo_destroy(bo);
+       drm_plane_state_put_back(state);
        return NULL;
 }
 
@@ -3047,6 +3054,29 @@ err:
        drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
 }
 
+/*
+ * Get the aspect-ratio from drmModeModeInfo mode flags.
+ *
+ * @param drm_mode_flags- flags from drmModeModeInfo structure.
+ * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'.
+ */
+static enum weston_mode_aspect_ratio
+drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags)
+{
+       return (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) >>
+               DRM_MODE_FLAG_PIC_AR_BITS_POS;
+}
+
+static const char *
+aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio)
+{
+       if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) ||
+           !aspect_ratio_as_string[ratio])
+               return " (unknown aspect ratio)";
+
+       return aspect_ratio_as_string[ratio];
+}
+
 static void
 drm_assign_planes(struct weston_output *output_base, void *repaint_data)
 {
@@ -3055,10 +3085,9 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
        struct drm_output *output = to_drm_output(output_base);
        struct drm_output_state *state;
        struct drm_plane_state *plane_state;
-       struct weston_view *ev, *next;
-       pixman_region32_t overlap, surface_overlap;
+       struct weston_view *ev;
+       pixman_region32_t surface_overlap, renderer_region;
        struct weston_plane *primary, *next_plane;
-       bool picked_scanout = false;
 
        assert(!output->state_last);
        state = drm_output_state_duplicate(output->state_cur,
@@ -3078,10 +3107,10 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
         * the client buffer can be used directly for the sprite surface
         * as we do for flipping full screen surfaces.
         */
-       pixman_region32_init(&overlap);
+       pixman_region32_init(&renderer_region);
        primary = &output_base->compositor->primary_plane;
 
-       wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) {
+       wl_list_for_each(ev, &output_base->compositor->view_list, link) {
                struct weston_surface *es = ev->surface;
 
                /* Test whether this buffer can ever go into a plane:
@@ -3102,23 +3131,17 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
                        es->keep_buffer = false;
 
                pixman_region32_init(&surface_overlap);
-               pixman_region32_intersect(&surface_overlap, &overlap,
+               pixman_region32_intersect(&surface_overlap, &renderer_region,
                                          &ev->transform.boundingbox);
 
                next_plane = NULL;
-               if (pixman_region32_not_empty(&surface_overlap) || picked_scanout)
+               if (pixman_region32_not_empty(&surface_overlap))
                        next_plane = primary;
                if (next_plane == NULL)
                        next_plane = drm_output_prepare_cursor_view(state, ev);
 
-               /* If a higher-stacked view already got assigned to scanout, it's incorrect to
-                * assign a subsequent (lower-stacked) view to scanout.
-                */
-               if (next_plane == NULL) {
+               if (next_plane == NULL)
                        next_plane = drm_output_prepare_scanout_view(state, ev);
-                       if (next_plane)
-                               picked_scanout = true;
-               }
 
                if (next_plane == NULL)
                        next_plane = drm_output_prepare_overlay_view(state, ev);
@@ -3129,7 +3152,8 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
                weston_view_move_to_plane(ev, next_plane);
 
                if (next_plane == primary)
-                       pixman_region32_union(&overlap, &overlap,
+                       pixman_region32_union(&renderer_region,
+                                             &renderer_region,
                                              &ev->transform.boundingbox);
 
                if (next_plane == primary ||
@@ -3146,7 +3170,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
 
                pixman_region32_fini(&surface_overlap);
        }
-       pixman_region32_fini(&overlap);
+       pixman_region32_fini(&renderer_region);
 
        /* We rely on output->cursor_view being both an accurate reflection of
         * the cursor plane's state, but also being maintained across repaints
@@ -3176,25 +3200,43 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data)
 static struct drm_mode *
 choose_mode (struct drm_output *output, struct weston_mode *target_mode)
 {
-       struct drm_mode *tmp_mode = NULL, *mode;
+       struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode;
+       enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE;
+       enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE;
+       struct drm_backend *b;
 
+       b = to_drm_backend(output->base.compositor);
+       target_aspect = target_mode->aspect_ratio;
+       src_aspect = output->base.current_mode->aspect_ratio;
        if (output->base.current_mode->width == target_mode->width &&
            output->base.current_mode->height == target_mode->height &&
            (output->base.current_mode->refresh == target_mode->refresh ||
-            target_mode->refresh == 0))
-               return to_drm_mode(output->base.current_mode);
+            target_mode->refresh == 0)) {
+               if (!b->aspect_ratio_supported || src_aspect == target_aspect)
+                       return to_drm_mode(output->base.current_mode);
+       }
 
        wl_list_for_each(mode, &output->base.mode_list, base.link) {
+
+               src_aspect = mode->base.aspect_ratio;
                if (mode->mode_info.hdisplay == target_mode->width &&
                    mode->mode_info.vdisplay == target_mode->height) {
                        if (mode->base.refresh == target_mode->refresh ||
                            target_mode->refresh == 0) {
-                               return mode;
-                       } else if (!tmp_mode)
+                               if (!b->aspect_ratio_supported ||
+                                   src_aspect == target_aspect)
+                                       return mode;
+                               else if (!mode_fall_back)
+                                       mode_fall_back = mode;
+                       } else if (!tmp_mode) {
                                tmp_mode = mode;
+                       }
                }
        }
 
+       if (mode_fall_back)
+               return mode_fall_back;
+
        return tmp_mode;
 }
 
@@ -3332,6 +3374,11 @@ init_kms_caps(struct drm_backend *b)
        weston_log("DRM: %s atomic modesetting\n",
                   b->atomic_modeset ? "supports" : "does not support");
 
+       ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1);
+       b->aspect_ratio_supported = (ret == 0);
+       weston_log("DRM: %s picture aspect ratio\n",
+                  b->aspect_ratio_supported ? "supports" : "does not support");
+
        return 0;
 }
 
@@ -3588,14 +3635,6 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output,
                /* On some platforms, primary/cursor planes can roam
                 * between different CRTCs, so make sure we don't claim the
                 * same plane for two outputs. */
-               wl_list_for_each(tmp, &b->compositor->pending_output_list,
-                                base.link) {
-                       if (tmp->cursor_plane == plane ||
-                           tmp->scanout_plane == plane) {
-                               found_elsewhere = true;
-                               break;
-                       }
-               }
                wl_list_for_each(tmp, &b->compositor->output_list,
                                 base.link) {
                        if (tmp->cursor_plane == plane ||
@@ -3695,6 +3734,25 @@ destroy_sprites(struct drm_backend *b)
                drm_plane_destroy(plane);
 }
 
+static uint32_t
+drm_refresh_rate_mHz(const drmModeModeInfo *info)
+{
+       uint64_t refresh;
+
+       /* Calculate higher precision (mHz) refresh rate */
+       refresh = (info->clock * 1000000LL / info->htotal +
+                  info->vtotal / 2) / info->vtotal;
+
+       if (info->flags & DRM_MODE_FLAG_INTERLACE)
+               refresh *= 2;
+       if (info->flags & DRM_MODE_FLAG_DBLSCAN)
+               refresh /= 2;
+       if (info->vscan > 1)
+           refresh /= info->vscan;
+
+       return refresh;
+}
+
 /**
  * Add a mode to output's mode list
  *
@@ -3709,7 +3767,6 @@ static struct drm_mode *
 drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info)
 {
        struct drm_mode *mode;
-       uint64_t refresh;
 
        mode = malloc(sizeof *mode);
        if (mode == NULL)
@@ -3719,24 +3776,15 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info)
        mode->base.width = info->hdisplay;
        mode->base.height = info->vdisplay;
 
-       /* Calculate higher precision (mHz) refresh rate */
-       refresh = (info->clock * 1000000LL / info->htotal +
-                  info->vtotal / 2) / info->vtotal;
-
-       if (info->flags & DRM_MODE_FLAG_INTERLACE)
-               refresh *= 2;
-       if (info->flags & DRM_MODE_FLAG_DBLSCAN)
-               refresh /= 2;
-       if (info->vscan > 1)
-           refresh /= info->vscan;
-
-       mode->base.refresh = refresh;
+       mode->base.refresh = drm_refresh_rate_mHz(info);
        mode->mode_info = *info;
        mode->blob_id = 0;
 
        if (info->type & DRM_MODE_TYPE_PREFERRED)
                mode->base.flags |= WL_OUTPUT_MODE_PREFERRED;
 
+       mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags);
+
        wl_list_insert(output->base.mode_list.prev, &mode->base.link);
 
        return mode;
@@ -3754,6 +3802,20 @@ drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode)
        free(mode);
 }
 
+/** Destroy a list of drm_modes
+ *
+ * @param backend The backend for releasing mode property blobs.
+ * @param mode_list The list linked by drm_mode::base.link.
+ */
+static void
+drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list)
+{
+       struct drm_mode *mode, *next;
+
+       wl_list_for_each_safe(mode, next, mode_list, base.link)
+               drm_output_destroy_mode(backend, mode);
+}
+
 static int
 drm_subpixel_to_wayland(int drm_value)
 {
@@ -3776,12 +3838,12 @@ drm_subpixel_to_wayland(int drm_value)
 
 /* returns a value between 0-255 range, where higher is brighter */
 static uint32_t
-drm_get_backlight(struct drm_output *output)
+drm_get_backlight(struct drm_head *head)
 {
        long brightness, max_brightness, norm;
 
-       brightness = backlight_get_brightness(output->backlight);
-       max_brightness = backlight_get_max_brightness(output->backlight);
+       brightness = backlight_get_brightness(head->backlight);
+       max_brightness = backlight_get_max_brightness(head->backlight);
 
        /* convert it on a scale of 0 to 255 */
        norm = (brightness * 255)/(max_brightness);
@@ -3794,21 +3856,53 @@ static void
 drm_set_backlight(struct weston_output *output_base, uint32_t value)
 {
        struct drm_output *output = to_drm_output(output_base);
+       struct drm_head *head;
        long max_brightness, new_brightness;
 
-       if (!output->backlight)
-               return;
-
        if (value > 255)
                return;
 
-       max_brightness = backlight_get_max_brightness(output->backlight);
+       wl_list_for_each(head, &output->base.head_list, base.output_link) {
+               if (!head->backlight)
+                       return;
 
-       /* get denormalized value */
-       new_brightness = (value * max_brightness) / 255;
+               max_brightness = backlight_get_max_brightness(head->backlight);
 
-       backlight_set_brightness(output->backlight, new_brightness);
-}
+               /* get denormalized value */
+               new_brightness = (value * max_brightness) / 255;
+
+               backlight_set_brightness(head->backlight, new_brightness);
+       }
+}
+
+static void
+drm_output_init_backlight(struct drm_output *output)
+{
+       struct weston_head *base;
+       struct drm_head *head;
+
+       output->base.set_backlight = NULL;
+
+       wl_list_for_each(base, &output->base.head_list, output_link) {
+               head = to_drm_head(base);
+
+               if (head->backlight) {
+                       weston_log("Initialized backlight for head '%s', device %s\n",
+                                  head->base.name, head->backlight->path);
+
+                       if (!output->base.set_backlight) {
+                               output->base.set_backlight = drm_set_backlight;
+                               output->base.backlight_current =
+                                                       drm_get_backlight(head);
+                       }
+               }
+       }
+
+       if (!output->base.set_backlight) {
+               weston_log("No backlight control for output '%s'\n",
+                          output->base.name);
+       }
+}
 
 /**
  * Power output on or off
@@ -3928,51 +4022,6 @@ make_connector_name(const drmModeConnector *con)
        return name;
 }
 
-static int
-find_crtc_for_connector(struct drm_backend *b,
-                       drmModeRes *resources, drmModeConnector *connector)
-{
-       drmModeEncoder *encoder;
-       int i, j;
-       int ret = -1;
-
-       for (j = 0; j < connector->count_encoders; j++) {
-               uint32_t possible_crtcs, encoder_id, crtc_id;
-
-               encoder = drmModeGetEncoder(b->drm.fd, connector->encoders[j]);
-               if (encoder == NULL) {
-                       weston_log("Failed to get encoder.\n");
-                       continue;
-               }
-               encoder_id = encoder->encoder_id;
-               possible_crtcs = encoder->possible_crtcs;
-               crtc_id = encoder->crtc_id;
-               drmModeFreeEncoder(encoder);
-
-               for (i = 0; i < resources->count_crtcs; i++) {
-                       if (!(possible_crtcs & (1 << i)))
-                               continue;
-
-                       if (drm_output_find_by_crtc(b, resources->crtcs[i]))
-                               continue;
-
-                       /* Try to preserve the existing
-                        * CRTC -> encoder -> connector routing; it makes
-                        * initialisation faster, and also since we have a
-                        * very dumb picking algorithm, may preserve a better
-                        * choice. */
-                       if (!connector->encoder_id ||
-                           (encoder_id == connector->encoder_id &&
-                            crtc_id == resources->crtcs[i]))
-                               return i;
-
-                       ret = i;
-               }
-       }
-
-       return ret;
-}
-
 static void drm_output_fini_cursor_egl(struct drm_output *output)
 {
        unsigned int i;
@@ -4002,8 +4051,7 @@ drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b)
                        goto err;
 
                output->gbm_cursor_fb[i] =
-                       drm_fb_get_from_bo(bo, b, GBM_FORMAT_ARGB8888,
-                                          BUFFER_CURSOR);
+                       drm_fb_get_from_bo(bo, b, false, BUFFER_CURSOR);
                if (!output->gbm_cursor_fb[i]) {
                        gbm_bo_destroy(bo);
                        goto err;
@@ -4087,6 +4135,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b)
        uint32_t format = output->gbm_format;
        uint32_t pixman_format;
        unsigned int i;
+       uint32_t flags = 0;
 
        switch (format) {
                case GBM_FORMAT_XRGB8888:
@@ -4114,8 +4163,14 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b)
                        goto err;
        }
 
-       if (pixman_renderer_output_create(&output->base) < 0)
-               goto err;
+       if (b->use_pixman_shadow)
+               flags |= PIXMAN_RENDERER_OUTPUT_USE_SHADOW;
+
+       if (pixman_renderer_output_create(&output->base, flags) < 0)
+               goto err;
+
+       weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name,
+                  b->use_pixman_shadow ? "uses" : "does not use");
 
        pixman_region32_init_rect(&output->previous_damage,
                                  output->base.x, output->base.y, output->base.width, output->base.height);
@@ -4264,8 +4319,7 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
 
 /** Parse monitor make, model and serial from EDID
  *
- * \param b The backend instance.
- * \param output The output whose \c drm_edid to fill in.
+ * \param head The head whose \c drm_edid to fill in.
  * \param props The DRM connector properties to get the EDID from.
  * \param make[out] The monitor make (PNP ID).
  * \param model[out] The monitor model (name).
@@ -4274,10 +4328,10 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
  * Each of \c *make, \c *model and \c *serial_number are set only if the
  * information is found in the EDID. The pointers they are set to must not
  * be free()'d explicitly, instead they get implicitly freed when the
- * \c drm_output is destroyed.
+ * \c drm_head is destroyed.
  */
 static void
-find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output,
+find_and_parse_output_edid(struct drm_head *head,
                           drmModeObjectPropertiesPtr props,
                           const char **make,
                           const char **model,
@@ -4288,29 +4342,25 @@ find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output,
        int rc;
 
        blob_id =
-               drm_property_get_value(&output->props_conn[WDRM_CONNECTOR_EDID],
+               drm_property_get_value(&head->props_conn[WDRM_CONNECTOR_EDID],
                                       props, 0);
        if (!blob_id)
                return;
 
-       edid_blob = drmModeGetPropertyBlob(b->drm.fd, blob_id);
+       edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id);
        if (!edid_blob)
                return;
 
-       rc = edid_parse(&output->edid,
+       rc = edid_parse(&head->edid,
                        edid_blob->data,
                        edid_blob->length);
        if (!rc) {
-               weston_log("EDID data '%s', '%s', '%s'\n",
-                          output->edid.pnp_id,
-                          output->edid.monitor_name,
-                          output->edid.serial_number);
-               if (output->edid.pnp_id[0] != '\0')
-                       *make = output->edid.pnp_id;
-               if (output->edid.monitor_name[0] != '\0')
-                       *model = output->edid.monitor_name;
-               if (output->edid.serial_number[0] != '\0')
-                       *serial_number = output->edid.serial_number;
+               if (head->edid.pnp_id[0] != '\0')
+                       *make = head->edid.pnp_id;
+               if (head->edid.monitor_name[0] != '\0')
+                       *model = head->edid.monitor_name;
+               if (head->edid.serial_number[0] != '\0')
+                       *serial_number = head->edid.serial_number;
        }
        drmModeFreePropertyBlob(edid_blob);
 }
@@ -4322,6 +4372,8 @@ parse_modeline(const char *s, drmModeModeInfo *mode)
        char vsync[16];
        float fclock;
 
+       memset(mode, 0, sizeof *mode);
+
        mode->type = DRM_MODE_TYPE_USERDEF;
        mode->hskew = 0;
        mode->vscan = 0;
@@ -4341,16 +4393,16 @@ parse_modeline(const char *s, drmModeModeInfo *mode)
                return -1;
 
        mode->clock = fclock * 1000;
-       if (strcmp(hsync, "+hsync") == 0)
+       if (strcasecmp(hsync, "+hsync") == 0)
                mode->flags |= DRM_MODE_FLAG_PHSYNC;
-       else if (strcmp(hsync, "-hsync") == 0)
+       else if (strcasecmp(hsync, "-hsync") == 0)
                mode->flags |= DRM_MODE_FLAG_NHSYNC;
        else
                return -1;
 
-       if (strcmp(vsync, "+vsync") == 0)
+       if (strcasecmp(vsync, "+vsync") == 0)
                mode->flags |= DRM_MODE_FLAG_PVSYNC;
-       else if (strcmp(vsync, "-vsync") == 0)
+       else if (strcasecmp(vsync, "-vsync") == 0)
                mode->flags |= DRM_MODE_FLAG_NVSYNC;
        else
                return -1;
@@ -4385,6 +4437,51 @@ setup_output_seat_constraint(struct drm_backend *b,
 }
 
 static int
+drm_output_attach_head(struct weston_output *output_base,
+                      struct weston_head *head_base)
+{
+       struct drm_backend *b = to_drm_backend(output_base->compositor);
+
+       if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS)
+               return -1;
+
+       if (!output_base->enabled)
+               return 0;
+
+       /* XXX: ensure the configuration will work.
+        * This is actually impossible without major infrastructure
+        * work. */
+
+       /* Need to go through modeset to add connectors. */
+       /* XXX: Ideally we'd do this per-output, not globally. */
+       /* XXX: Doing it globally, what guarantees another output's update
+        * will not clear the flag before this output is updated?
+        */
+       b->state_invalid = true;
+
+       weston_output_schedule_repaint(output_base);
+
+       return 0;
+}
+
+static void
+drm_output_detach_head(struct weston_output *output_base,
+                      struct weston_head *head_base)
+{
+       struct drm_backend *b = to_drm_backend(output_base->compositor);
+
+       if (!output_base->enabled)
+               return;
+
+       /* Need to go through modeset to drop connectors that should no longer
+        * be driven. */
+       /* XXX: Ideally we'd do this per-output, not globally. */
+       b->state_invalid = true;
+
+       weston_output_schedule_repaint(output_base);
+}
+
+static int
 parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format)
 {
        int ret = 0;
@@ -4405,6 +4502,137 @@ parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format)
        return ret;
 }
 
+static uint32_t
+u32distance(uint32_t a, uint32_t b)
+{
+       if (a < b)
+               return b - a;
+       else
+               return a - b;
+}
+
+/** Choose equivalent mode
+ *
+ * If the two modes are not equivalent, return NULL.
+ * Otherwise return the mode that is more likely to work in place of both.
+ *
+ * None of the fuzzy matching criteria in this function have any justification.
+ *
+ * typedef struct _drmModeModeInfo {
+ *         uint32_t clock;
+ *         uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
+ *         uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
+ *
+ *         uint32_t vrefresh;
+ *
+ *         uint32_t flags;
+ *         uint32_t type;
+ *         char name[DRM_DISPLAY_MODE_LEN];
+ * } drmModeModeInfo, *drmModeModeInfoPtr;
+ */
+static const drmModeModeInfo *
+drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b)
+{
+       uint32_t refresh_a, refresh_b;
+
+       if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay)
+               return NULL;
+
+       if (a->flags != b->flags)
+               return NULL;
+
+       /* kHz */
+       if (u32distance(a->clock, b->clock) > 500)
+               return NULL;
+
+       refresh_a = drm_refresh_rate_mHz(a);
+       refresh_b = drm_refresh_rate_mHz(b);
+       if (u32distance(refresh_a, refresh_b) > 50)
+               return NULL;
+
+       if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) {
+               if (a->type & DRM_MODE_TYPE_PREFERRED)
+                       return a;
+               else
+                       return b;
+       }
+
+       return a;
+}
+
+/* If the given mode info is not already in the list, add it.
+ * If it is in the list, either keep the existing or replace it,
+ * depending on which one is "better".
+ */
+static int
+drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info)
+{
+       struct weston_mode *base;
+       struct drm_mode *mode;
+       struct drm_backend *backend;
+       const drmModeModeInfo *chosen = NULL;
+
+       assert(info);
+
+       wl_list_for_each(base, &output->base.mode_list, link) {
+               mode = to_drm_mode(base);
+               chosen = drm_mode_pick_equivalent(&mode->mode_info, info);
+               if (chosen)
+                       break;
+       }
+
+       if (chosen == info) {
+               backend = to_drm_backend(output->base.compositor);
+               drm_output_destroy_mode(backend, mode);
+               chosen = NULL;
+       }
+
+       if (!chosen) {
+               mode = drm_output_add_mode(output, info);
+               if (!mode)
+                       return -1;
+       }
+       /* else { the equivalent mode is already in the list } */
+
+       return 0;
+}
+
+/** Rewrite the output's mode list
+ *
+ * @param output The output.
+ * @return 0 on success, -1 on failure.
+ *
+ * Destroy all existing modes in the list, and reconstruct a new list from
+ * scratch, based on the currently attached heads.
+ *
+ * On failure the output's mode list may contain some modes.
+ */
+static int
+drm_output_update_modelist_from_heads(struct drm_output *output)
+{
+       struct drm_backend *backend = to_drm_backend(output->base.compositor);
+       struct weston_head *head_base;
+       struct drm_head *head;
+       int i;
+       int ret;
+
+       assert(!output->base.enabled);
+
+       drm_mode_list_destroy(backend, &output->base.mode_list);
+
+       wl_list_for_each(head_base, &output->base.head_list, output_link) {
+               head = to_drm_head(head_base);
+               for (i = 0; i < head->connector->count_modes; i++) {
+                       ret = drm_output_try_add_mode(output,
+                                               &head->connector->modes[i]);
+                       if (ret < 0)
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
 /**
  * Choose suitable mode for an output
  *
@@ -4429,17 +4657,35 @@ drm_output_choose_initial_mode(struct drm_backend *backend,
        struct drm_mode *preferred = NULL;
        struct drm_mode *current = NULL;
        struct drm_mode *configured = NULL;
+       struct drm_mode *config_fall_back = NULL;
        struct drm_mode *best = NULL;
        struct drm_mode *drm_mode;
        drmModeModeInfo drm_modeline;
        int32_t width = 0;
        int32_t height = 0;
        uint32_t refresh = 0;
+       uint32_t aspect_width = 0;
+       uint32_t aspect_height = 0;
+       enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE;
        int n;
 
        if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) {
-               n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh);
-               if (n != 2 && n != 3) {
+               n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height,
+                          &refresh, &aspect_width, &aspect_height);
+               if (backend->aspect_ratio_supported && n == 5) {
+                       if (aspect_width == 4 && aspect_height == 3)
+                               aspect_ratio = WESTON_MODE_PIC_AR_4_3;
+                       else if (aspect_width == 16 && aspect_height == 9)
+                               aspect_ratio = WESTON_MODE_PIC_AR_16_9;
+                       else if (aspect_width == 64 && aspect_height == 27)
+                               aspect_ratio = WESTON_MODE_PIC_AR_64_27;
+                       else if (aspect_width == 256 && aspect_height == 135)
+                               aspect_ratio = WESTON_MODE_PIC_AR_256_135;
+                       else
+                               weston_log("Invalid modeline \"%s\" for output %s\n",
+                                          modeline, output->base.name);
+               }
+               if (n != 2 && n != 3 && n != 5) {
                        width = -1;
 
                        if (parse_modeline(modeline, &drm_modeline) == 0) {
@@ -4456,8 +4702,13 @@ drm_output_choose_initial_mode(struct drm_backend *backend,
        wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) {
                if (width == drm_mode->base.width &&
                    height == drm_mode->base.height &&
-                   (refresh == 0 || refresh == drm_mode->mode_info.vrefresh))
-                       configured = drm_mode;
+                   (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) {
+                       if (!backend->aspect_ratio_supported ||
+                           aspect_ratio == drm_mode->base.aspect_ratio)
+                               configured = drm_mode;
+                       else
+                               config_fall_back = drm_mode;
+               }
 
                if (memcmp(current_mode, &drm_mode->mode_info,
                           sizeof *current_mode) == 0)
@@ -4481,6 +4732,9 @@ drm_output_choose_initial_mode(struct drm_backend *backend,
        if (configured)
                return configured;
 
+       if (config_fall_back)
+               return config_fall_back;
+
        if (preferred)
                return preferred;
 
@@ -4495,23 +4749,25 @@ drm_output_choose_initial_mode(struct drm_backend *backend,
 }
 
 static int
-connector_get_current_mode(drmModeConnector *connector, int drm_fd,
-                          drmModeModeInfo *mode)
+drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend)
 {
+       int drm_fd = backend->drm.fd;
        drmModeEncoder *encoder;
        drmModeCrtc *crtc;
 
        /* Get the current mode on the crtc that's currently driving
         * this connector. */
-       encoder = drmModeGetEncoder(drm_fd, connector->encoder_id);
-       memset(mode, 0, sizeof *mode);
+       encoder = drmModeGetEncoder(drm_fd, head->connector->encoder_id);
        if (encoder != NULL) {
+               head->inherited_crtc_id = encoder->crtc_id;
+
                crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id);
                drmModeFreeEncoder(encoder);
+
                if (crtc == NULL)
                        return -1;
                if (crtc->mode_valid)
-                       *mode = crtc->mode;
+                       head->inherited_mode = crtc->mode;
                drmModeFreeCrtc(crtc);
        }
 
@@ -4525,14 +4781,15 @@ drm_output_set_mode(struct weston_output *base,
 {
        struct drm_output *output = to_drm_output(base);
        struct drm_backend *b = to_drm_backend(base->compositor);
+       struct drm_head *head = to_drm_head(weston_output_get_first_head(base));
 
        struct drm_mode *current;
-       drmModeModeInfo crtc_mode;
 
-       if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0)
+       if (drm_output_update_modelist_from_heads(output) < 0)
                return -1;
 
-       current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode);
+       current = drm_output_choose_initial_mode(b, output, mode, modeline,
+                                                &head->inherited_mode);
        if (!current)
                return -1;
 
@@ -4593,14 +4850,161 @@ drm_output_init_gamma_size(struct drm_output *output)
        return 0;
 }
 
+static uint32_t
+drm_head_get_possible_crtcs_mask(struct drm_head *head)
+{
+       uint32_t possible_crtcs = 0;
+       drmModeEncoder *encoder;
+       int i;
+
+       for (i = 0; i < head->connector->count_encoders; i++) {
+               encoder = drmModeGetEncoder(head->backend->drm.fd,
+                                           head->connector->encoders[i]);
+               if (!encoder)
+                       continue;
+
+               possible_crtcs |= encoder->possible_crtcs;
+               drmModeFreeEncoder(encoder);
+       }
+
+       return possible_crtcs;
+}
+
+static int
+drm_crtc_get_index(drmModeRes *resources, uint32_t crtc_id)
+{
+       int i;
+
+       for (i = 0; i < resources->count_crtcs; i++) {
+               if (resources->crtcs[i] == crtc_id)
+                       return i;
+       }
+
+       assert(0 && "unknown crtc id");
+       return -1;
+}
+
+/** Pick a CRTC that might be able to drive all attached connectors
+ *
+ * @param output The output whose attached heads to include.
+ * @param resources The DRM KMS resources.
+ * @return CRTC index, or -1 on failure or not found.
+ */
+static int
+drm_output_pick_crtc(struct drm_output *output, drmModeRes *resources)
+{
+       struct drm_backend *backend;
+       struct weston_head *base;
+       struct drm_head *head;
+       uint32_t possible_crtcs = 0xffffffff;
+       int existing_crtc[32];
+       unsigned j, n = 0;
+       uint32_t crtc_id;
+       int best_crtc_index = -1;
+       int fallback_crtc_index = -1;
+       int i;
+       bool match;
+
+       backend = to_drm_backend(output->base.compositor);
+
+       /* This algorithm ignores drmModeEncoder::possible_clones restriction,
+        * because it is more often set wrong than not in the kernel. */
+
+       /* Accumulate a mask of possible crtcs and find existing routings. */
+       wl_list_for_each(base, &output->base.head_list, output_link) {
+               head = to_drm_head(base);
+
+               possible_crtcs &= drm_head_get_possible_crtcs_mask(head);
+
+               crtc_id = head->inherited_crtc_id;
+               if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc))
+                       existing_crtc[n++] = drm_crtc_get_index(resources,
+                                                               crtc_id);
+       }
+
+       /* Find a crtc that could drive each connector individually at least,
+        * and prefer existing routings. */
+       for (i = 0; i < resources->count_crtcs; i++) {
+               crtc_id = resources->crtcs[i];
+
+               /* Could the crtc not drive each connector? */
+               if (!(possible_crtcs & (1 << i)))
+                       continue;
+
+               /* Is the crtc already in use? */
+               if (drm_output_find_by_crtc(backend, crtc_id))
+                       continue;
+
+               /* Try to preserve the existing CRTC -> connector routing;
+                * it makes initialisation faster, and also since we have a
+                * very dumb picking algorithm, may preserve a better
+                * choice. */
+               for (j = 0; j < n; j++) {
+                       if (existing_crtc[j] == i)
+                               return i;
+               }
+
+               /* Check if any other head had existing routing to this CRTC.
+                * If they did, this is not the best CRTC as it might be needed
+                * for another output we haven't enabled yet. */
+               match = false;
+               wl_list_for_each(base, &backend->compositor->head_list,
+                                compositor_link) {
+                       head = to_drm_head(base);
+
+                       if (head->base.output == &output->base)
+                               continue;
+
+                       if (weston_head_is_enabled(&head->base))
+                               continue;
+
+                       if (head->inherited_crtc_id == crtc_id) {
+                               match = true;
+                               break;
+                       }
+               }
+               if (!match)
+                       best_crtc_index = i;
+
+               fallback_crtc_index = i;
+       }
+
+       if (best_crtc_index != -1)
+               return best_crtc_index;
+
+       if (fallback_crtc_index != -1)
+               return fallback_crtc_index;
+
+       /* Likely possible_crtcs was empty due to asking for clones,
+        * but since the DRM documentation says the kernel lies, let's
+        * pick one crtc anyway. Trial and error is the only way to
+        * be sure if something doesn't work. */
+
+       /* First pick any existing assignment. */
+       for (j = 0; j < n; j++) {
+               crtc_id = resources->crtcs[existing_crtc[j]];
+               if (!drm_output_find_by_crtc(backend, crtc_id))
+                       return existing_crtc[j];
+       }
+
+       /* Otherwise pick any available crtc. */
+       for (i = 0; i < resources->count_crtcs; i++) {
+               crtc_id = resources->crtcs[i];
+
+               if (!drm_output_find_by_crtc(backend, crtc_id))
+                       return i;
+       }
+
+       return -1;
+}
+
 /** Allocate a CRTC for the output
  *
  * @param output The output with no allocated CRTC.
  * @param resources DRM KMS resources.
- * @param connector The DRM KMS connector data.
  * @return 0 on success, -1 on failure.
  *
- * Finds a free CRTC that can drive the given connector, reserves the CRTC
+ * Finds a free CRTC that might drive the attached connectors, reserves the CRTC
  * for the output, and loads the CRTC properties.
  *
  * Populates the cursor and scanout planes.
@@ -4608,8 +5012,7 @@ drm_output_init_gamma_size(struct drm_output *output)
  * On failure, the output remains without a CRTC.
  */
 static int
-drm_output_init_crtc(struct drm_output *output,
-                    drmModeRes *resources, drmModeConnector *connector)
+drm_output_init_crtc(struct drm_output *output, drmModeRes *resources)
 {
        struct drm_backend *b = to_drm_backend(output->base.compositor);
        drmModeObjectPropertiesPtr props;
@@ -4617,9 +5020,10 @@ drm_output_init_crtc(struct drm_output *output,
 
        assert(output->crtc_id == 0);
 
-       i = find_crtc_for_connector(b, resources, connector);
+       i = drm_output_pick_crtc(output, resources);
        if (i < 0) {
-               weston_log("No usable crtc/encoder pair for connector.\n");
+               weston_log("Output '%s': No available CRTCs.\n",
+                          output->base.name);
                return -1;
        }
 
@@ -4651,6 +5055,8 @@ drm_output_init_crtc(struct drm_output *output,
                drm_output_find_special_plane(b, output,
                                              WDRM_PLANE_TYPE_CURSOR);
 
+       wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id);
+
        return 0;
 
 err_crtc:
@@ -4670,6 +5076,7 @@ static void
 drm_output_fini_crtc(struct drm_output *output)
 {
        struct drm_backend *b = to_drm_backend(output->base.compositor);
+       uint32_t *unused;
 
        if (!b->universal_planes && !b->shutting_down) {
                /* With universal planes, the 'special' planes are allocated at
@@ -4691,17 +5098,62 @@ drm_output_fini_crtc(struct drm_output *output)
        }
 
        drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT);
+
+       assert(output->crtc_id != 0);
+
+       unused = wl_array_add(&b->unused_crtcs, sizeof(*unused));
+       *unused = output->crtc_id;
+
+       /* Force resetting unused CRTCs */
+       b->state_invalid = true;
+
        output->crtc_id = 0;
        output->cursor_plane = NULL;
        output->scanout_plane = NULL;
 }
 
+static void
+drm_output_print_modes(struct drm_output *output)
+{
+       struct weston_mode *m;
+       struct drm_mode *dm;
+       const char *aspect_ratio;
+
+       wl_list_for_each(m, &output->base.mode_list, link) {
+               dm = to_drm_mode(m);
+
+               aspect_ratio = aspect_ratio_to_string(m->aspect_ratio);
+               weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n",
+                                   m->width, m->height, m->refresh / 1000.0,
+                                   aspect_ratio,
+                                   m->flags & WL_OUTPUT_MODE_PREFERRED ?
+                                   ", preferred" : "",
+                                   m->flags & WL_OUTPUT_MODE_CURRENT ?
+                                   ", current" : "",
+                                   dm->mode_info.clock / 1000.0);
+       }
+}
+
 static int
 drm_output_enable(struct weston_output *base)
 {
        struct drm_output *output = to_drm_output(base);
        struct drm_backend *b = to_drm_backend(base->compositor);
-       struct weston_mode *m;
+       drmModeRes *resources;
+       int ret;
+
+       resources = drmModeGetResources(b->drm.fd);
+       if (!resources) {
+               weston_log("drmModeGetResources failed\n");
+               return -1;
+       }
+       ret = drm_output_init_crtc(output, resources);
+       drmModeFreeResources(resources);
+       if (ret < 0)
+               return -1;
+
+       if (drm_output_init_gamma_size(output) < 0)
+               goto err;
 
        if (b->pageflip_timeout)
                drm_output_pageflip_timer_create(output);
@@ -4716,14 +5168,7 @@ drm_output_enable(struct weston_output *base)
                goto err;
        }
 
-       if (output->backlight) {
-               weston_log("Initialized backlight, device %s\n",
-                          output->backlight->path);
-               output->base.set_backlight = drm_set_backlight;
-               output->base.backlight_current = drm_get_backlight(output);
-       } else {
-               weston_log("Failed to initialize backlight\n");
-       }
+       drm_output_init_backlight(output);
 
        output->base.start_repaint_loop = drm_output_start_repaint_loop;
        output->base.repaint = drm_output_repaint;
@@ -4743,24 +5188,15 @@ drm_output_enable(struct weston_output *base)
                                      &output->scanout_plane->base,
                                      &b->compositor->primary_plane);
 
-       wl_array_remove_uint32(&b->unused_connectors, output->connector_id);
-       wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id);
-
-       weston_log("Output %s, (connector %d, crtc %d)\n",
-                  output->base.name, output->connector_id, output->crtc_id);
-       wl_list_for_each(m, &output->base.mode_list, link)
-               weston_log_continue(STAMP_SPACE "mode %dx%d@%.1f%s%s%s\n",
-                                   m->width, m->height, m->refresh / 1000.0,
-                                   m->flags & WL_OUTPUT_MODE_PREFERRED ?
-                                   ", preferred" : "",
-                                   m->flags & WL_OUTPUT_MODE_CURRENT ?
-                                   ", current" : "",
-                                   output->connector->count_modes == 0 ?
-                                   ", built-in" : "");
+       weston_log("Output %s (crtc %d) video modes:\n",
+                  output->base.name, output->crtc_id);
+       drm_output_print_modes(output);
 
        return 0;
 
 err:
+       drm_output_fini_crtc(output);
+
        return -1;
 }
 
@@ -4769,7 +5205,6 @@ drm_output_deinit(struct weston_output *base)
 {
        struct drm_output *output = to_drm_output(base);
        struct drm_backend *b = to_drm_backend(base->compositor);
-       uint32_t *unused;
 
        if (b->use_pixman)
                drm_output_fini_pixman(output);
@@ -4792,21 +5227,17 @@ drm_output_deinit(struct weston_output *base)
                }
        }
 
-       unused = wl_array_add(&b->unused_connectors, sizeof(*unused));
-       *unused = output->connector_id;
-       unused = wl_array_add(&b->unused_crtcs, sizeof(*unused));
-       *unused = output->crtc_id;
-
-       /* Force programming unused connectors and crtcs. */
-       b->state_invalid = true;
+       drm_output_fini_crtc(output);
 }
 
 static void
+drm_head_destroy(struct drm_head *head);
+
+static void
 drm_output_destroy(struct weston_output *base)
 {
        struct drm_output *output = to_drm_output(base);
        struct drm_backend *b = to_drm_backend(base->compositor);
-       struct drm_mode *drm_mode, *next;
 
        if (output->page_flip_pending || output->vblank_pending ||
            output->atomic_complete_pending) {
@@ -4818,23 +5249,13 @@ drm_output_destroy(struct weston_output *base)
        if (output->base.enabled)
                drm_output_deinit(&output->base);
 
-       wl_list_for_each_safe(drm_mode, next, &output->base.mode_list,
-                             base.link)
-               drm_output_destroy_mode(b, drm_mode);
+       drm_mode_list_destroy(b, &output->base.mode_list);
 
        if (output->pageflip_timer)
                wl_event_source_remove(output->pageflip_timer);
 
        weston_output_release(&output->base);
 
-       drm_output_fini_crtc(output);
-
-       drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT);
-       drmModeFreeConnector(output->connector);
-
-       if (output->backlight)
-               backlight_destroy(output->backlight);
-
        assert(!output->state_last);
        drm_output_state_free(output->state_cur);
 
@@ -4865,7 +5286,7 @@ drm_output_disable(struct weston_output *base)
 /**
  * Update the list of unused connectors and CRTCs
  *
- * This keeps the unused_connectors and unused_crtcs arrays up to date.
+ * This keeps the unused_crtc arrays up to date.
  *
  * @param b Weston backend structure
  * @param resources DRM resources for this device
@@ -4875,22 +5296,6 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources)
 {
        int i;
 
-       wl_array_release(&b->unused_connectors);
-       wl_array_init(&b->unused_connectors);
-
-       for (i = 0; i < resources->count_connectors; i++) {
-               struct drm_output *output;
-               uint32_t *connector_id;
-
-               output = drm_output_find_by_connector(b, resources->connectors[i]);
-               if (output && output->base.enabled)
-                       continue;
-
-               connector_id = wl_array_add(&b->unused_connectors,
-                                           sizeof(*connector_id));
-               *connector_id = resources->connectors[i];
-       }
-
        wl_array_release(&b->unused_crtcs);
        wl_array_init(&b->unused_crtcs);
 
@@ -4907,113 +5312,233 @@ drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources)
        }
 }
 
-/**
- * Create a Weston output structure
+/** Replace connector data and monitor information
  *
- * Given a DRM connector, create a matching drm_output structure and add it
- * to Weston's output list. It also takes ownership of the connector, which
- * is released when output is destroyed.
+ * @param head The head to update.
+ * @param connector The connector data to be owned by the head, must match
+ * the head's connector ID.
+ * @return 0 on success, -1 on failure.
  *
- * @param b Weston backend structure
- * @param resources DRM resources for this device
- * @param connector DRM connector to use for this new output
- * @param drm_device udev device pointer
- * @returns 0 on success, or -1 on failure
+ * Takes ownership of @c connector on success, not on failure.
+ *
+ * May schedule a heads changed call.
  */
 static int
-create_output_for_connector(struct drm_backend *b,
-                           drmModeRes *resources,
-                           drmModeConnector *connector,
-                           struct udev_device *drm_device)
+drm_head_assign_connector_info(struct drm_head *head,
+                              drmModeConnector *connector)
 {
-       struct drm_output *output;
-       drmModeObjectPropertiesPtr props;
-       struct drm_mode *drm_mode;
-       char *name;
+       drmModeObjectProperties *props;
        const char *make = "unknown";
        const char *model = "unknown";
        const char *serial_number = "unknown";
-       int i;
 
-       output = zalloc(sizeof *output);
-       if (output == NULL)
-               goto err_init;
+       assert(connector);
+       assert(head->connector_id == connector->connector_id);
+
+       props = drmModeObjectGetProperties(head->backend->drm.fd,
+                                          head->connector_id,
+                                          DRM_MODE_OBJECT_CONNECTOR);
+       if (!props) {
+               weston_log("Error: failed to get connector '%s' properties\n",
+                          head->base.name);
+               return -1;
+       }
+
+       if (head->connector)
+               drmModeFreeConnector(head->connector);
+       head->connector = connector;
+
+       drm_property_info_populate(head->backend, connector_props,
+                                  head->props_conn,
+                                  WDRM_CONNECTOR__COUNT, props);
+       find_and_parse_output_edid(head, props, &make, &model, &serial_number);
+       weston_head_set_monitor_strings(&head->base, make, model, serial_number);
+       weston_head_set_subpixel(&head->base,
+               drm_subpixel_to_wayland(head->connector->subpixel));
+
+       weston_head_set_physical_size(&head->base, head->connector->mmWidth,
+                                     head->connector->mmHeight);
+
+       drmModeFreeObjectProperties(props);
+
+       /* Unknown connection status is assumed disconnected. */
+       weston_head_set_connection_status(&head->base,
+                       head->connector->connection == DRM_MODE_CONNECTED);
+
+       return 0;
+}
+
+static void
+drm_head_log_info(struct drm_head *head, const char *msg)
+{
+       if (head->base.connected) {
+               weston_log("DRM: head '%s' %s, connector %d is connected, "
+                          "EDID make '%s', model '%s', serial '%s'\n",
+                          head->base.name, msg, head->connector_id,
+                          head->base.make, head->base.model,
+                          head->base.serial_number ?: "");
+       } else {
+               weston_log("DRM: head '%s' %s, connector %d is disconnected.\n",
+                          head->base.name, msg, head->connector_id);
+       }
+}
+
+/** Update connector and monitor information
+ *
+ * @param head The head to update.
+ *
+ * Re-reads the DRM property lists for the connector and updates monitor
+ * information and connection status. This may schedule a heads changed call
+ * to the user.
+ */
+static void
+drm_head_update_info(struct drm_head *head)
+{
+       drmModeConnector *connector;
+
+       connector = drmModeGetConnector(head->backend->drm.fd,
+                                       head->connector_id);
+       if (!connector) {
+               weston_log("DRM: getting connector info for '%s' failed.\n",
+                          head->base.name);
+               return;
+       }
+
+       if (drm_head_assign_connector_info(head, connector) < 0)
+               drmModeFreeConnector(connector);
 
-       output->connector = connector;
-       output->connector_id = connector->connector_id;
+       if (head->base.device_changed)
+               drm_head_log_info(head, "updated");
+}
+
+/**
+ * Create a Weston head for a connector
+ *
+ * Given a DRM connector, create a matching drm_head structure and add it
+ * to Weston's head list.
+ *
+ * @param b Weston backend structure
+ * @param connector_id DRM connector ID for the head
+ * @param drm_device udev device pointer
+ * @returns The new head, or NULL on failure.
+ */
+static struct drm_head *
+drm_head_create(struct drm_backend *backend, uint32_t connector_id,
+               struct udev_device *drm_device)
+{
+       struct drm_head *head;
+       drmModeConnector *connector;
+       char *name;
 
-       output->backlight = backlight_init(drm_device,
-                                          connector->connector_type);
+       head = zalloc(sizeof *head);
+       if (!head)
+               return NULL;
+
+       connector = drmModeGetConnector(backend->drm.fd, connector_id);
+       if (!connector)
+               goto err_alloc;
 
        name = make_connector_name(connector);
-       weston_output_init(&output->base, b->compositor, name);
+       if (!name)
+               goto err_alloc;
+
+       weston_head_init(&head->base, name);
        free(name);
 
-       output->base.enable = drm_output_enable;
-       output->base.destroy = drm_output_destroy;
-       output->base.disable = drm_output_disable;
+       head->connector_id = connector_id;
+       head->backend = backend;
 
-       output->destroy_pending = 0;
-       output->disable_pending = 0;
+       head->backlight = backlight_init(drm_device, connector->connector_type);
 
-       if (drm_output_init_crtc(output, resources, connector) < 0)
-               goto err_output;
+       if (drm_head_assign_connector_info(head, connector) < 0)
+               goto err_init;
 
-       props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id,
-                                          DRM_MODE_OBJECT_CONNECTOR);
-       if (!props) {
-               weston_log("failed to get connector properties\n");
-               goto err_output;
+       if (head->connector->connector_type == DRM_MODE_CONNECTOR_LVDS ||
+           head->connector->connector_type == DRM_MODE_CONNECTOR_eDP)
+               weston_head_set_internal(&head->base);
+
+       if (drm_head_read_current_setup(head, backend) < 0) {
+               weston_log("Failed to retrieve current mode from connector %d.\n",
+                          head->connector_id);
+               /* Not fatal. */
        }
-       drm_property_info_populate(b, connector_props, output->props_conn,
-                                  WDRM_CONNECTOR__COUNT, props);
-       find_and_parse_output_edid(b, output, props,
-                                  &make, &model, &serial_number);
-       output->base.make = (char *)make;
-       output->base.model = (char *)model;
-       output->base.serial_number = (char *)serial_number;
-       output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel);
 
-       drmModeFreeObjectProperties(props);
+       weston_compositor_add_head(backend->compositor, &head->base);
+       drm_head_log_info(head, "found");
 
-       if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS ||
-           output->connector->connector_type == DRM_MODE_CONNECTOR_eDP)
-               output->base.connection_internal = true;
+       return head;
 
-       if (drm_output_init_gamma_size(output) < 0)
-               goto err_output;
+err_init:
+       weston_head_release(&head->base);
 
-       output->state_cur = drm_output_state_alloc(output, NULL);
+err_alloc:
+       if (connector)
+               drmModeFreeConnector(connector);
 
-       output->base.mm_width = output->connector->mmWidth;
-       output->base.mm_height = output->connector->mmHeight;
+       free(head);
 
-       for (i = 0; i < output->connector->count_modes; i++) {
-               drm_mode = drm_output_add_mode(output, &output->connector->modes[i]);
-               if (!drm_mode) {
-                       weston_log("failed to add mode\n");
-                       goto err_output;
-               }
-       }
+       return NULL;
+}
 
-       weston_compositor_add_pending_output(&output->base, b->compositor);
+static void
+drm_head_destroy(struct drm_head *head)
+{
+       weston_head_release(&head->base);
 
-       return 0;
+       drm_property_info_free(head->props_conn, WDRM_CONNECTOR__COUNT);
+       drmModeFreeConnector(head->connector);
 
-err_output:
-       drm_output_destroy(&output->base);
-       return -1;
-       /* no fallthrough! */
+       if (head->backlight)
+               backlight_destroy(head->backlight);
 
-err_init:
-       drmModeFreeConnector(connector);
-       return -1;
+       free(head);
+}
+
+/**
+ * Create a Weston output structure
+ *
+ * Create an "empty" drm_output. This is the implementation of
+ * weston_backend::create_output.
+ *
+ * Creating an output is usually followed by drm_output_attach_head()
+ * and drm_output_enable() to make use of it.
+ *
+ * @param compositor The compositor instance.
+ * @param name Name for the new output.
+ * @returns The output, or NULL on failure.
+ */
+static struct weston_output *
+drm_output_create(struct weston_compositor *compositor, const char *name)
+{
+       struct drm_backend *b = to_drm_backend(compositor);
+       struct drm_output *output;
+
+       output = zalloc(sizeof *output);
+       if (output == NULL)
+               return NULL;
+
+       weston_output_init(&output->base, compositor, name);
+
+       output->base.enable = drm_output_enable;
+       output->base.destroy = drm_output_destroy;
+       output->base.disable = drm_output_disable;
+       output->base.attach_head = drm_output_attach_head;
+       output->base.detach_head = drm_output_detach_head;
+
+       output->destroy_pending = 0;
+       output->disable_pending = 0;
+
+       output->state_cur = drm_output_state_alloc(output, NULL);
+
+       weston_compositor_add_pending_output(&output->base, b->compositor);
+
+       return &output->base;
 }
 
 static int
-create_outputs(struct drm_backend *b, struct udev_device *drm_device)
+drm_backend_create_heads(struct drm_backend *b, struct udev_device *drm_device)
 {
-       drmModeConnector *connector;
+       struct drm_head *head;
        drmModeRes *resources;
        int i;
 
@@ -5029,41 +5554,28 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device)
        b->max_height = resources->max_height;
 
        for (i = 0; i < resources->count_connectors; i++) {
-               int ret;
-
-               connector = drmModeGetConnector(b->drm.fd,
-                                               resources->connectors[i]);
-               if (connector == NULL)
-                       continue;
+               uint32_t connector_id = resources->connectors[i];
 
-               if (connector->connection == DRM_MODE_CONNECTED) {
-                       ret = create_output_for_connector(b, resources,
-                                                         connector, drm_device);
-                       if (ret < 0)
-                               weston_log("failed to create new connector\n");
-               } else {
-                       drmModeFreeConnector(connector);
+               head = drm_head_create(b, connector_id, drm_device);
+               if (!head) {
+                       weston_log("DRM: failed to create head for connector %d.\n",
+                                  connector_id);
                }
        }
 
        drm_backend_update_unused_outputs(b, resources);
 
-       if (wl_list_empty(&b->compositor->output_list) &&
-           wl_list_empty(&b->compositor->pending_output_list))
-               weston_log("No currently active connector found.\n");
-
        drmModeFreeResources(resources);
 
        return 0;
 }
 
 static void
-update_outputs(struct drm_backend *b, struct udev_device *drm_device)
+drm_backend_update_heads(struct drm_backend *b, struct udev_device *drm_device)
 {
-       drmModeConnector *connector;
        drmModeRes *resources;
-       struct drm_output *output, *next;
-       uint32_t *connected;
+       struct weston_head *base, *next;
+       struct drm_head *head;
        int i;
 
        resources = drmModeGetResources(b->drm.fd);
@@ -5072,76 +5584,45 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device)
                return;
        }
 
-       connected = calloc(resources->count_connectors, sizeof(uint32_t));
-       if (!connected) {
-               drmModeFreeResources(resources);
-               return;
-       }
-
-       /* collect new connects */
+       /* collect new connectors that have appeared, e.g. MST */
        for (i = 0; i < resources->count_connectors; i++) {
                uint32_t connector_id = resources->connectors[i];
 
-               connector = drmModeGetConnector(b->drm.fd, connector_id);
-               if (connector == NULL)
-                       continue;
-
-               if (connector->connection != DRM_MODE_CONNECTED) {
-                       drmModeFreeConnector(connector);
-                       continue;
-               }
-
-               connected[i] = connector_id;
-
-               if (drm_output_find_by_connector(b, connector_id)) {
-                       drmModeFreeConnector(connector);
-                       continue;
+               head = drm_head_find_by_connector(b, connector_id);
+               if (head) {
+                       drm_head_update_info(head);
+               } else {
+                       head = drm_head_create(b, connector_id, drm_device);
+                       if (!head)
+                               weston_log("DRM: failed to create head for hot-added connector %d.\n",
+                                          connector_id);
                }
-
-               create_output_for_connector(b, resources,
-                                           connector, drm_device);
-               weston_log("connector %d connected\n", connector_id);
        }
 
-       wl_list_for_each_safe(output, next, &b->compositor->output_list,
-                             base.link) {
-               bool disconnected = true;
-
-               for (i = 0; i < resources->count_connectors; i++) {
-                       if (connected[i] == output->connector_id) {
-                               disconnected = false;
-                               break;
-                       }
-               }
+       /* Remove connectors that have disappeared. */
+       wl_list_for_each_safe(base, next,
+                             &b->compositor->head_list, compositor_link) {
+               bool removed = true;
 
-               if (!disconnected)
-                       continue;
-
-               weston_log("connector %d disconnected\n", output->connector_id);
-               drm_output_destroy(&output->base);
-       }
-
-       wl_list_for_each_safe(output, next, &b->compositor->pending_output_list,
-                             base.link) {
-               bool disconnected = true;
+               head = to_drm_head(base);
 
                for (i = 0; i < resources->count_connectors; i++) {
-                       if (connected[i] == output->connector_id) {
-                               disconnected = false;
+                       if (resources->connectors[i] == head->connector_id) {
+                               removed = false;
                                break;
                        }
                }
 
-               if (!disconnected)
+               if (!removed)
                        continue;
 
-               weston_log("connector %d disconnected\n", output->connector_id);
-               drm_output_destroy(&output->base);
+               weston_log("DRM: head '%s' (connector %d) disappeared.\n",
+                          head->base.name, head->connector_id);
+               drm_head_destroy(head);
        }
 
        drm_backend_update_unused_outputs(b, resources);
 
-       free(connected);
        drmModeFreeResources(resources);
 }
 
@@ -5171,7 +5652,7 @@ udev_drm_event(int fd, uint32_t mask, void *data)
        event = udev_monitor_receive_device(b->udev_monitor);
 
        if (udev_event_is_hotplug(b, event))
-               update_outputs(b, event);
+               drm_backend_update_heads(b, event);
 
        udev_device_unref(event);
 
@@ -5182,6 +5663,7 @@ static void
 drm_destroy(struct weston_compositor *ec)
 {
        struct drm_backend *b = to_drm_backend(ec);
+       struct weston_head *base, *next;
 
        udev_input_destroy(&b->input);
 
@@ -5194,17 +5676,21 @@ drm_destroy(struct weston_compositor *ec)
 
        weston_compositor_shutdown(ec);
 
+       wl_list_for_each_safe(base, next, &ec->head_list, compositor_link)
+               drm_head_destroy(to_drm_head(base));
+
        if (b->gbm)
                gbm_device_destroy(b->gbm);
 
+       udev_monitor_unref(b->udev_monitor);
        udev_unref(b->udev);
 
        weston_launcher_destroy(ec->launcher);
 
        wl_array_release(&b->unused_crtcs);
-       wl_array_release(&b->unused_connectors);
 
        close(b->drm.fd);
+       free(b->drm.filename);
        free(b);
 }
 
@@ -5624,8 +6110,16 @@ drm_backend_create(struct weston_compositor *compositor,
        struct udev_device *drm_device;
        struct wl_event_loop *loop;
        const char *seat_id = default_seat;
+       const char *session_seat;
        int ret;
 
+       session_seat = getenv("XDG_SEAT");
+       if (session_seat)
+               seat_id = session_seat;
+
+       if (config->seat_id)
+               seat_id = config->seat_id;
+
        weston_log("initializing drm backend\n");
 
        b = zalloc(sizeof *b);
@@ -5635,7 +6129,6 @@ drm_backend_create(struct weston_compositor *compositor,
        b->state_invalid = true;
        b->drm.fd = -1;
        wl_array_init(&b->unused_crtcs);
-       wl_array_init(&b->unused_connectors);
 
        /*
         * KMS support for hardware planes cannot properly synchronize
@@ -5651,15 +6144,13 @@ drm_backend_create(struct weston_compositor *compositor,
        b->compositor = compositor;
        b->use_pixman = config->use_pixman;
        b->pageflip_timeout = config->pageflip_timeout;
+       b->use_pixman_shadow = config->use_pixman_shadow;
 
        compositor->backend = &b->base;
 
        if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0)
                goto err_compositor;
 
-       if (config->seat_id)
-               seat_id = config->seat_id;
-
        /* Check if we run drm-backend using weston-launch */
        compositor->launcher = weston_launcher_connect(compositor, config->tty,
                                                       seat_id, true);
@@ -5709,6 +6200,7 @@ drm_backend_create(struct weston_compositor *compositor,
        b->base.repaint_begin = drm_repaint_begin;
        b->base.repaint_flush = drm_repaint_flush;
        b->base.repaint_cancel = drm_repaint_cancel;
+       b->base.create_output = drm_output_create;
 
        weston_setup_vt_switch_bindings(compositor);
 
@@ -5722,8 +6214,8 @@ drm_backend_create(struct weston_compositor *compositor,
                goto err_sprite;
        }
 
-       if (create_outputs(b, drm_device) < 0) {
-               weston_log("failed to create output for %s\n", b->drm.filename);
+       if (drm_backend_create_heads(b, drm_device) < 0) {
+               weston_log("Failed to create heads for %s\n", b->drm.filename);
                goto err_udev_input;
        }
 
@@ -5809,6 +6301,7 @@ err_compositor:
 static void
 config_init_to_defaults(struct weston_drm_backend_config *config)
 {
+       config->use_pixman_shadow = true;
 }
 
 WL_EXPORT int