weston: support clone mode on DRM-frontend
Add a new output section key "same-as" for configuring clone mode. An
output marked "same-as" another output will be configured identically to
the other output.
The current implementation supports only CRTC sharing for clone mode.
Independent CRTC clone mode cannot be supported until output layout
logic is moved from libweston into the frontend and libweston's damage
tracking issues stemming from overlapping outputs are solved.
Quite a lot of infrastructure is needed to properly configure clone
mode. The implemented logic allows easy addition of independent CRTC
clone mode once libweston supports it. The idea is that wet_layoutput is
the item to be laid out and all weston_outputs a wet_layoutput
contains show exactly the same area of the desktop.
The configuration logic attempts to automatically fall back to creating
more weston_outputs when all heads do not work under the same
weston_output. For now, the fallback path ends with an error message.
Enabling a weston_output is bit complicated, because one needs to first
collect all relevant heads, try to attach them all to the weston_output,
and then back up head by head until enabling the weston_output succeeds.
A new weston_output is created for the left-over heads and the process
is repeated.
CRTC-sharing clone mode is the most efficient clone mode, offering
synchronized scanout timings, but it is not always supported by
hardware.
v10:
- rebased trivial conflicts in man page
- switch to gitlab issue URL
v9:
- replace weston_compositor_set_heads_changed_cb() with
weston_compositor_add_heads_changed_listener()
- remove workaround in simple_head_enable()
v6:
- Add man-page note about cms-colord.
- Don't create an output just to turn it off.
Fixes: https://gitlab.freedesktop.org/wayland/weston/issues/22
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
Acked-by: Derek Foreman <derekf@osg.samsung.com>
Acked-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Ian Ray <ian.ray@ge.com>
diff --git a/compositor/main.c b/compositor/main.c
index 2cb50c1..3891134 100644
--- a/compositor/main.c
+++ b/compositor/main.c
@@ -71,11 +71,41 @@
};
struct wet_compositor;
+struct wet_layoutput;
struct wet_head_tracker {
struct wl_listener head_destroy_listener;
};
+/** User data for each weston_output */
+struct wet_output {
+ struct weston_output *output;
+ struct wl_listener output_destroy_listener;
+ struct wet_layoutput *layoutput;
+ struct wl_list link; /**< in wet_layoutput::output_list */
+};
+
+#define MAX_CLONE_HEADS 16
+
+struct wet_head_array {
+ struct weston_head *heads[MAX_CLONE_HEADS]; /**< heads to add */
+ unsigned n; /**< the number of heads */
+};
+
+/** A layout output
+ *
+ * Contains wet_outputs that are all clones (independent CRTCs).
+ * Stores output layout information in the future.
+ */
+struct wet_layoutput {
+ struct wet_compositor *compositor;
+ struct wl_list compositor_link; /**< in wet_compositor::layoutput_list */
+ struct wl_list output_list; /**< wet_output::link */
+ char *name;
+ struct weston_config_section *section;
+ struct wet_head_array add; /**< tmp: heads to add as clones */
+};
+
struct wet_compositor {
struct weston_compositor *compositor;
struct weston_config *config;
@@ -84,6 +114,7 @@
struct wl_listener heads_changed_listener;
int (*simple_output_configure)(struct weston_output *output);
bool init_failed;
+ struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */
};
static FILE *weston_logfile = NULL;
@@ -1178,12 +1209,6 @@
struct weston_output *output;
int ret = 0;
- /* Workaround for repeated DRM backend "off" setting.
- * For any other case, we should not have an attached head that is not
- * enabled. */
- if (weston_head_get_output(head))
- return;
-
output = weston_compositor_create_output_with_head(wet->compositor,
head);
if (!output) {
@@ -1205,10 +1230,6 @@
return;
}
- /* Escape hatch for DRM backend "off" setting. */
- if (ret > 0)
- return;
-
if (weston_output_enable(output) < 0) {
weston_log("Enabling output \"%s\" failed.\n",
weston_head_get_name(head));
@@ -1304,32 +1325,29 @@
}
static int
-drm_backend_output_configure(struct weston_output *output)
+drm_backend_output_configure(struct weston_output *output,
+ struct weston_config_section *section)
{
- struct weston_config *wc = wet_get_config(output->compositor);
struct wet_compositor *wet = to_wet_compositor(output->compositor);
- struct weston_config_section *section;
- const struct weston_drm_output_api *api = weston_drm_output_get_api(output->compositor);
+ const struct weston_drm_output_api *api;
enum weston_drm_backend_output_mode mode =
WESTON_DRM_BACKEND_OUTPUT_PREFERRED;
-
char *s;
char *modeline = NULL;
char *gbm_format = NULL;
char *seat = NULL;
+ api = weston_drm_output_get_api(output->compositor);
if (!api) {
weston_log("Cannot use weston_drm_output_api.\n");
return -1;
}
- section = weston_config_get_section(wc, "output", "name", output->name);
weston_config_section_get_string(section, "mode", &s, "preferred");
if (strcmp(s, "off") == 0) {
- weston_output_disable(output);
- free(s);
- return 1;
+ assert(0 && "off was supposed to be pruned");
+ return -1;
} else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) {
mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT;
} else if (strcmp(s, "preferred") != 0) {
@@ -1362,6 +1380,434 @@
return 0;
}
+/* Find the output section to use for configuring the output with the
+ * named head. If an output section with the given name contains
+ * a "same-as" key, ignore all other settings in the output section and
+ * instead find an output section named by the "same-as". Do this
+ * recursively.
+ */
+static struct weston_config_section *
+drm_config_find_controlling_output_section(struct weston_config *config,
+ const char *head_name)
+{
+ struct weston_config_section *section;
+ char *same_as;
+ int depth = 0;
+
+ same_as = strdup(head_name);
+ do {
+ section = weston_config_get_section(config, "output",
+ "name", same_as);
+ if (!section && depth > 0)
+ weston_log("Configuration error: "
+ "output section referred to with "
+ "'same-as=%s' not found.\n", same_as);
+
+ free(same_as);
+
+ if (!section)
+ return NULL;
+
+ if (++depth > 10) {
+ weston_log("Configuration error: "
+ "'same-as' nested too deep for output '%s'.\n",
+ head_name);
+ return NULL;
+ }
+
+ weston_config_section_get_string(section, "same-as",
+ &same_as, NULL);
+ } while (same_as);
+
+ return section;
+}
+
+static struct wet_layoutput *
+wet_compositor_create_layoutput(struct wet_compositor *compositor,
+ const char *name,
+ struct weston_config_section *section)
+{
+ struct wet_layoutput *lo;
+
+ lo = zalloc(sizeof *lo);
+ if (!lo)
+ return NULL;
+
+ lo->compositor = compositor;
+ wl_list_insert(compositor->layoutput_list.prev, &lo->compositor_link);
+ wl_list_init(&lo->output_list);
+ lo->name = strdup(name);
+ lo->section = section;
+
+ return lo;
+}
+
+static void
+wet_layoutput_destroy(struct wet_layoutput *lo)
+{
+ wl_list_remove(&lo->compositor_link);
+ assert(wl_list_empty(&lo->output_list));
+ free(lo->name);
+ free(lo);
+}
+
+static void
+wet_output_handle_destroy(struct wl_listener *listener, void *data)
+{
+ struct wet_output *output;
+
+ output = wl_container_of(listener, output, output_destroy_listener);
+ assert(output->output == data);
+
+ output->output = NULL;
+ wl_list_remove(&output->output_destroy_listener.link);
+}
+
+static struct wet_output *
+wet_layoutput_create_output(struct wet_layoutput *lo, const char *name)
+{
+ struct wet_output *output;
+
+ output = zalloc(sizeof *output);
+ if (!output)
+ return NULL;
+
+ output->output =
+ weston_compositor_create_output(lo->compositor->compositor,
+ name);
+ if (!output) {
+ free(output);
+ return NULL;
+ }
+
+ output->layoutput = lo;
+ wl_list_insert(lo->output_list.prev, &output->link);
+ output->output_destroy_listener.notify = wet_output_handle_destroy;
+ weston_output_add_destroy_listener(output->output,
+ &output->output_destroy_listener);
+
+ return output;
+}
+
+static struct wet_output *
+wet_output_from_weston_output(struct weston_output *base)
+{
+ struct wl_listener *lis;
+
+ lis = weston_output_get_destroy_listener(base,
+ wet_output_handle_destroy);
+ if (!lis)
+ return NULL;
+
+ return container_of(lis, struct wet_output, output_destroy_listener);
+}
+
+static void
+wet_output_destroy(struct wet_output *output)
+{
+ if (output->output)
+ weston_output_destroy(output->output);
+
+ wl_list_remove(&output->link);
+ free(output);
+}
+
+static struct wet_layoutput *
+wet_compositor_find_layoutput(struct wet_compositor *wet, const char *name)
+{
+ struct wet_layoutput *lo;
+
+ wl_list_for_each(lo, &wet->layoutput_list, compositor_link)
+ if (strcmp(lo->name, name) == 0)
+ return lo;
+
+ return NULL;
+}
+
+static void
+wet_compositor_layoutput_add_head(struct wet_compositor *wet,
+ const char *output_name,
+ struct weston_config_section *section,
+ struct weston_head *head)
+{
+ struct wet_layoutput *lo;
+
+ lo = wet_compositor_find_layoutput(wet, output_name);
+ if (!lo) {
+ lo = wet_compositor_create_layoutput(wet, output_name, section);
+ if (!lo)
+ return;
+ }
+
+ if (lo->add.n + 1 >= ARRAY_LENGTH(lo->add.heads))
+ return;
+
+ lo->add.heads[lo->add.n++] = head;
+}
+
+static void
+wet_compositor_destroy_layout(struct wet_compositor *wet)
+{
+ struct wet_layoutput *lo, *lo_tmp;
+ struct wet_output *output, *output_tmp;
+
+ wl_list_for_each_safe(lo, lo_tmp,
+ &wet->layoutput_list, compositor_link) {
+ wl_list_for_each_safe(output, output_tmp,
+ &lo->output_list, link) {
+ wet_output_destroy(output);
+ }
+ wet_layoutput_destroy(lo);
+ }
+}
+
+static void
+drm_head_prepare_enable(struct wet_compositor *wet,
+ struct weston_head *head)
+{
+ const char *name = weston_head_get_name(head);
+ struct weston_config_section *section;
+ char *output_name = NULL;
+ char *mode = NULL;
+
+ section = drm_config_find_controlling_output_section(wet->config, name);
+ if (section) {
+ /* skip outputs that are explicitly off, the backend turns
+ * them off automatically.
+ */
+ weston_config_section_get_string(section, "mode", &mode, NULL);
+ if (mode && strcmp(mode, "off") == 0) {
+ free(mode);
+ return;
+ }
+ free(mode);
+
+ weston_config_section_get_string(section, "name",
+ &output_name, NULL);
+ assert(output_name);
+
+ wet_compositor_layoutput_add_head(wet, output_name,
+ section, head);
+ free(output_name);
+ } else {
+ wet_compositor_layoutput_add_head(wet, name, NULL, head);
+ }
+}
+
+static void
+drm_try_attach(struct weston_output *output,
+ struct wet_head_array *add,
+ struct wet_head_array *failed)
+{
+ unsigned i;
+
+ /* try to attach all heads, this probably succeeds */
+ for (i = 0; i < add->n; i++) {
+ if (!add->heads[i])
+ continue;
+
+ if (weston_output_attach_head(output, add->heads[i]) < 0) {
+ assert(failed->n < ARRAY_LENGTH(failed->heads));
+
+ failed->heads[failed->n++] = add->heads[i];
+ add->heads[i] = NULL;
+ }
+ }
+}
+
+static int
+drm_try_enable(struct weston_output *output,
+ struct wet_head_array *undo,
+ struct wet_head_array *failed)
+{
+ /* Try to enable, and detach heads one by one until it succeeds. */
+ while (!output->enabled) {
+ if (weston_output_enable(output) == 0)
+ return 0;
+
+ /* the next head to drop */
+ while (undo->n > 0 && undo->heads[--undo->n] == NULL)
+ ;
+
+ /* No heads left to undo and failed to enable. */
+ if (undo->heads[undo->n] == NULL)
+ return -1;
+
+ assert(failed->n < ARRAY_LENGTH(failed->heads));
+
+ /* undo one head */
+ weston_head_detach(undo->heads[undo->n]);
+ failed->heads[failed->n++] = undo->heads[undo->n];
+ undo->heads[undo->n] = NULL;
+ }
+
+ return 0;
+}
+
+static int
+drm_try_attach_enable(struct weston_output *output, struct wet_layoutput *lo)
+{
+ struct wet_head_array failed = {};
+ unsigned i;
+
+ assert(!output->enabled);
+
+ drm_try_attach(output, &lo->add, &failed);
+ if (drm_backend_output_configure(output, lo->section) < 0)
+ return -1;
+
+ if (drm_try_enable(output, &lo->add, &failed) < 0)
+ return -1;
+
+ /* For all successfully attached/enabled heads */
+ for (i = 0; i < lo->add.n; i++)
+ if (lo->add.heads[i])
+ wet_head_tracker_create(lo->compositor,
+ lo->add.heads[i]);
+
+ /* Push failed heads to the next round. */
+ lo->add = failed;
+
+ return 0;
+}
+
+static int
+drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo)
+{
+ struct wet_output *output, *tmp;
+ char *name = NULL;
+ int ret;
+
+ /*
+ * For each existing wet_output:
+ * try attach
+ * While heads left to enable:
+ * Create output
+ * try attach, try enable
+ */
+
+ wl_list_for_each_safe(output, tmp, &lo->output_list, link) {
+ struct wet_head_array failed = {};
+
+ if (!output->output) {
+ /* Clean up left-overs from destroyed heads. */
+ wet_output_destroy(output);
+ continue;
+ }
+
+ assert(output->output->enabled);
+
+ drm_try_attach(output->output, &lo->add, &failed);
+ lo->add = failed;
+ if (lo->add.n == 0)
+ return 0;
+ }
+
+ if (!weston_compositor_find_output_by_name(wet->compositor, lo->name))
+ name = strdup(lo->name);
+
+ while (lo->add.n > 0) {
+ if (!wl_list_empty(&lo->output_list)) {
+ weston_log("Error: independent-CRTC clone mode is not implemented.\n");
+ return -1;
+ }
+
+ if (!name) {
+ ret = asprintf(&name, "%s:%s", lo->name,
+ weston_head_get_name(lo->add.heads[0]));
+ if (ret < 0)
+ return -1;
+ }
+ output = wet_layoutput_create_output(lo, name);
+ free(name);
+ name = NULL;
+
+ if (!output)
+ return -1;
+
+ if (drm_try_attach_enable(output->output, lo) < 0) {
+ wet_output_destroy(output);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+drm_process_layoutputs(struct wet_compositor *wet)
+{
+ struct wet_layoutput *lo;
+ int ret = 0;
+
+ wl_list_for_each(lo, &wet->layoutput_list, compositor_link) {
+ if (lo->add.n == 0)
+ continue;
+
+ if (drm_process_layoutput(wet, lo) < 0) {
+ lo->add = (struct wet_head_array){};
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+static void
+drm_head_disable(struct weston_head *head)
+{
+ struct weston_output *output_base;
+ struct wet_output *output;
+ struct wet_head_tracker *track;
+
+ track = wet_head_tracker_from_head(head);
+ if (track)
+ wet_head_tracker_destroy(track);
+
+ output_base = weston_head_get_output(head);
+ assert(output_base);
+ output = wet_output_from_weston_output(output_base);
+ assert(output && output->output == output_base);
+
+ weston_head_detach(head);
+ if (count_remaining_heads(output->output, NULL) == 0)
+ wet_output_destroy(output);
+}
+
+static void
+drm_heads_changed(struct wl_listener *listener, void *arg)
+{
+ struct weston_compositor *compositor = arg;
+ struct wet_compositor *wet = to_wet_compositor(compositor);
+ struct weston_head *head = NULL;
+ bool connected;
+ bool enabled;
+ bool changed;
+
+ /* We need to collect all cloned heads into outputs before enabling the
+ * output.
+ */
+ while ((head = weston_compositor_iterate_heads(compositor, head))) {
+ connected = weston_head_is_connected(head);
+ enabled = weston_head_is_enabled(head);
+ changed = weston_head_is_device_changed(head);
+
+ if (connected && !enabled) {
+ drm_head_prepare_enable(wet, head);
+ } else if (!connected && enabled) {
+ drm_head_disable(head);
+ } else if (enabled && changed) {
+ weston_log("Detected a monitor change on head '%s', "
+ "not bothering to do anything about it.\n",
+ weston_head_get_name(head));
+ }
+ weston_head_reset_device_changed(head);
+ }
+
+ if (drm_process_layoutputs(wet) < 0)
+ wet->init_failed = true;
+}
+
static int
load_drm_backend(struct weston_compositor *c,
int *argc, char **argv, struct weston_config *wc)
@@ -1397,7 +1843,9 @@
config.base.struct_size = sizeof(struct weston_drm_backend_config);
config.configure_device = configure_input_device;
- wet_set_simple_head_configurator(c, drm_backend_output_configure);
+ wet->heads_changed_listener.notify = drm_heads_changed;
+ weston_compositor_add_heads_changed_listener(c,
+ &wet->heads_changed_listener);
ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM,
&config.base);
@@ -1936,6 +2384,8 @@
{ WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger },
};
+ wl_list_init(&wet.layoutput_list);
+
if (os_fd_set_cloexec(fileno(stdin))) {
printf("Unable to set stdin as close on exec().\n");
return EXIT_FAILURE;
@@ -2123,6 +2573,8 @@
ret = wet.compositor->exit_code;
out:
+ wet_compositor_destroy_layout(&wet);
+
/* free(NULL) is valid, and it won't be NULL if it's used */
free(wet.parsed_options);
diff --git a/man/weston-drm.man b/man/weston-drm.man
index d4cb75a..c9e6915 100644
--- a/man/weston-drm.man
+++ b/man/weston-drm.man
@@ -83,6 +83,18 @@
\fBpixman-shadow\fR=\fIboolean\fR
If using the Pixman-renderer, use shadow framebuffers. Defaults to
.BR true .
+.TP
+\fBsame-as\fR=\fIname\fR
+Make this output (connector) a clone of another. The argument
+.IR name " is the "
+.BR name " value of another output section. The
+referred to output section must exist. When this key is present in an
+output section, all other keys have no effect on the configuration.
+
+NOTE: cms-colord plugin does not work correctly with this option. The plugin
+chooses an arbitrary monitor to load the color profile for, but the
+profile is applied equally to all cloned monitors regardless of their
+properties.
.
.\" ***************************************************************
.SH OPTIONS