From f005f25d62e048877dee96564a6b8c25a2b8752a Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 10 Nov 2017 16:34:39 +0200 Subject: [PATCH] compositor-drm: combine mode list from heads A single list of modes needs to be combined from the mode lists in each attached head. We could just concatenate the lists, but that might introduce duplicates. Try to avoid duplicates instead by using partially fuzzy matching. When a duplicate is found, try to figure out which is more suitable to use in place of both. If one has the preferred flag and the other doesn't, take the preferred one. Otherwise use the one already in the list. Signed-off-by: Pekka Paalanen Reviewed-by: Daniel Stone Acked-by: Derek Foreman --- libweston/compositor-drm.c | 114 +++++++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 10 deletions(-) diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 750f458f..9840f331 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -4428,6 +4428,101 @@ 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. @@ -4444,22 +4539,21 @@ 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; - struct drm_mode *mode; int i; + int ret; assert(!output->base.enabled); drm_mode_list_destroy(backend, &output->base.mode_list); - /* XXX: needs a strategy for combining mode lists from multiple heads */ - head_base = weston_output_get_first_head(&output->base); - assert(head_base); - head = to_drm_head(head_base); - - for (i = 0; i < head->connector->count_modes; i++) { - mode = drm_output_add_mode(output, &head->connector->modes[i]); - if (!mode) - return -1; + 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;