diff --git a/frontend/main.c b/frontend/main.c index 02c238485..281a1bb41 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -2270,16 +2270,15 @@ wet_output_set_color_format(struct weston_output *output, entry = weston_enum_map_find_name(color_formats, str); if (!entry) { - char *mask_to_str = NULL; + unsigned int i; + weston_log("Error in config for output '%s': '%s' is not a " "valid color format. Try one of: ", output->name, str); - mask_to_str = bits_to_str(WESTON_COLOR_FORMAT_ALL_MASK, - weston_color_format_to_str); - weston_log_continue("%s\n", mask_to_str); - - free(mask_to_str); + for (i = 0; i < ARRAY_LENGTH(color_formats); i++) + weston_log_continue(" %s", color_formats[i].name); + weston_log_continue("\n"); free(str); return -1; } diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index d4ba085d0..99c25e612 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -293,6 +293,9 @@ struct drm_mode { struct weston_mode base; drmModeModeInfo mode_info; uint32_t blob_id; + uint8_t vic; + bool can_yuv420; + bool must_yuv420; }; enum drm_fb_type { @@ -576,6 +579,10 @@ struct drm_head { void *display_data; /**< EDID or DisplayID blob */ size_t display_data_len; /**< bytes */ + + bool yuv420_vics; + bool can_yuv420_vic_map[256]; + bool must_yuv420_vic_map[256]; }; struct drm_crtc { diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index ab0ce11f1..96b8ffb0c 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -57,6 +57,10 @@ struct drm_head_info { /* The monitor supported color foramts, combination of * enum_weston_color_format bits. */ uint32_t color_format_mask; + + bool yuv420_vics; + bool must_yuv420_vic_map[256]; + bool can_yuv420_vic_map[256]; }; static void @@ -100,6 +104,27 @@ drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags) } } +static enum di_cta_video_format_picture_aspect_ratio +drm_to_cta_aspect_ratio(uint32_t drm_mode_flags, bool *valid) +{ + *valid = true; + + switch (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) { + case DRM_MODE_FLAG_PIC_AR_4_3: + return DI_CTA_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3; + case DRM_MODE_FLAG_PIC_AR_16_9: + return DI_CTA_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_16_9; + case DRM_MODE_FLAG_PIC_AR_64_27: + return DI_CTA_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_64_27; + case DRM_MODE_FLAG_PIC_AR_256_135: + return DI_CTA_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_256_135; + case DRM_MODE_FLAG_PIC_AR_NONE: + default: + *valid = false; + return DI_CTA_VIDEO_FORMAT_PICTURE_ASPECT_RATIO_4_3; + } +} + static const char * aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio) { @@ -288,25 +313,91 @@ get_colorimetry_mask(const struct di_info *info) return mask; } -static bool -has_yuv420_cap_map(const struct di_edid_cta *cta) +/* Much of the yuv420 capability check code is duplicated or barely modified + * from: + * libdisplay-info (https://gitlab.freedesktop.org/emersion/libdisplay-info) + * + * However, bugs should be considered an original work of the Weston project. + */ +static void +add_yuv420_only_block(struct drm_head_info *dhi, + const struct di_edid_cta *cta, + const struct di_cta_data_block *yuv420_data_block) { - const struct di_cta_data_block *const *data_blocks; - enum di_cta_data_block_tag db_tag; - int i; + const struct di_cta_svd *const *svds; + int i; - data_blocks = di_edid_cta_get_data_blocks(cta); - for (i = 0; data_blocks[i] != NULL; i++) { - db_tag = di_cta_data_block_get_tag(data_blocks[i]); - if (db_tag == DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP) - return true; + svds = di_cta_data_block_get_ycbcr420_svds(yuv420_data_block); + if (!svds) + return; + + for (i = 0; svds[i] != NULL; i++) { + dhi->yuv420_vics = true; + dhi->must_yuv420_vic_map[svds[i]->vic] = true; + } +} + +static void +add_map_to_yuv420_caps(struct drm_head_info *dhi, + const struct di_edid_cta *cta, + const struct di_cta_data_block *yuv420_cap_map_block) +{ + const struct di_cta_ycbcr420_cap_map *ycbcr420_cap_map; + const struct di_cta_data_block *const *data_blocks; + const struct di_cta_data_block *data_block; + const struct di_cta_svd *const *svds = NULL; + enum di_cta_data_block_tag db_tag; + int i, j, svd_index = 0; + + ycbcr420_cap_map = di_cta_data_block_get_ycbcr420_cap_map(yuv420_cap_map_block); + + data_blocks = di_edid_cta_get_data_blocks(cta); + for (i = 0; data_blocks[i] != NULL; i++) { + data_block = data_blocks[i]; + db_tag = di_cta_data_block_get_tag(data_block); + if (db_tag != DI_CTA_DATA_BLOCK_VIDEO) + continue; + svds = di_cta_data_block_get_svds(data_block); + + for (j = 0; svds[j] != NULL; j++) { + if (di_cta_ycbcr420_cap_map_supported(ycbcr420_cap_map, svd_index)) { + dhi->yuv420_vics = true; + dhi->can_yuv420_vic_map[svds[j]->vic] = true; + } + svd_index++; + } + } +} + +static void +add_cta_to_yuv420_caps(struct drm_head_info *dhi, + const struct di_edid_cta *cta) +{ + const struct di_cta_data_block *const *data_blocks; + const struct di_cta_data_block *data_block; + enum di_cta_data_block_tag db_tag; + int i; + + data_blocks = di_edid_cta_get_data_blocks(cta); + for (i = 0; data_blocks[i] != NULL; i++) { + data_block = data_blocks[i]; + db_tag = di_cta_data_block_get_tag(data_block); + switch (db_tag) { + case DI_CTA_DATA_BLOCK_YCBCR420: + add_yuv420_only_block(dhi, cta, data_block); + break; + case DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP: + add_map_to_yuv420_caps(dhi, cta, data_block); + break; + default: + break; + } } - - return false; } static uint32_t -get_color_format_mask(const struct di_info *info) +get_color_format_mask(struct drm_head_info *dhi, + const struct di_info *info) { const struct di_edid *edid = di_info_get_edid(info); const struct di_edid_color_encoding_formats *fmts = @@ -343,18 +434,16 @@ get_color_format_mask(const struct di_info *info) if (cta_flags->ycc422) mask |= WESTON_COLOR_FORMAT_YUV422; - /* FIXME: YUV420 detection is really complicated, - * do it properly at some point... - */ - if (has_yuv420_cap_map(cta)) - mask |= WESTON_COLOR_FORMAT_YUV420; - - break; + add_cta_to_yuv420_caps(dhi, cta); + break; default: break; } } + if (dhi->yuv420_vics) + mask |= WESTON_COLOR_FORMAT_YUV420; + return mask; } @@ -374,6 +463,10 @@ drm_head_info_from_edid(struct drm_head_info *dhi, return NULL; } + dhi->yuv420_vics = 0; + memset(dhi->can_yuv420_vic_map, 0, sizeof(dhi->can_yuv420_vic_map)); + memset(dhi->must_yuv420_vic_map, 0, sizeof(dhi->must_yuv420_vic_map)); + msg = di_info_get_failure_msg(di_ctx); if (msg) weston_log("DRM: EDID for the following head fails conformity:\n%s\n", msg); @@ -383,7 +476,7 @@ drm_head_info_from_edid(struct drm_head_info *dhi, dhi->serial_number = di_info_get_serial(di_ctx); dhi->eotf_mask = get_eotf_mask(di_ctx); dhi->colorimetry_mask = get_colorimetry_mask(di_ctx); - dhi->color_format_mask = get_color_format_mask(di_ctx); + dhi->color_format_mask = get_color_format_mask(dhi, di_ctx); return di_ctx; } @@ -509,6 +602,85 @@ drm_refresh_rate_mHz(const drmModeModeInfo *info) return refresh; } +static bool +drmmodeinfo_is_fmt(const drmModeModeInfo *info, + const struct di_cta_video_format *fmt) +{ + int vfront = info->vsync_start - info->vdisplay; + int vsync = info->vsync_end - info->vsync_start; + int vback = info->vtotal - info->vsync_end; + int hfront = info->hsync_start - info->hdisplay; + int hsync = info->hsync_end - info->hsync_start; + int hback = info->htotal - info->hsync_end; + uint32_t fmt_clock_khz = fmt->pixel_clock_hz / 1000; + enum di_cta_video_format_sync_polarity hpol, vpol; + bool interlaced = !!(info->flags & DRM_MODE_FLAG_INTERLACE); + enum di_cta_video_format_picture_aspect_ratio info_ar; + bool valid_ar; + + if (info->flags & DRM_MODE_FLAG_PHSYNC) + hpol = DI_CTA_VIDEO_FORMAT_SYNC_POSITIVE; + else if (info->flags & DRM_MODE_FLAG_NHSYNC) + hpol = DI_CTA_VIDEO_FORMAT_SYNC_NEGATIVE; + else + return false; + + if (info->flags & DRM_MODE_FLAG_PVSYNC) + vpol = DI_CTA_VIDEO_FORMAT_SYNC_POSITIVE; + else if (info->flags & DRM_MODE_FLAG_NVSYNC) + vpol = DI_CTA_VIDEO_FORMAT_SYNC_NEGATIVE; + else + return false; + + info_ar = drm_to_cta_aspect_ratio(info->flags, &valid_ar); + if (!valid_ar) + return false; + + /* The clock match is imprecise because it's already rounded off + * in drmModeModeInfo. + */ + if (info->hdisplay != fmt->h_active || + info->vdisplay != fmt->v_active || + hfront != fmt->h_front || + vfront != fmt->v_front || + hsync != fmt->h_sync || + vsync != fmt->v_sync || + hback != fmt->h_back || + vback != fmt->v_back || + info->clock != fmt_clock_khz || + hpol != fmt->h_sync_polarity || + vpol != fmt->v_sync_polarity || + interlaced != fmt->interlaced || + info_ar != fmt->picture_aspect_ratio) + return false; + + return true; +} + +static int +match_cta_mode(const drmModeModeInfo *info) +{ + const struct di_cta_video_format *fmt; + uint8_t vic; + + /* FIXME: Some day we'll probably get this from libdi high- + * level api, but for now we kludge it here. + * + * The stop at 219 works around an off by one bug in some + * releases of libdi, and needs to be 255 when we can + * bump version to ensure the fix. + */ + for (vic = 0; vic < 220; vic++) { + fmt = di_cta_video_format_from_vic(vic); + if (!fmt) + continue; + assert(fmt->vic == vic); + if (drmmodeinfo_is_fmt(info, fmt)) + return vic; + } + return 0; +} + /** * Add a mode to output's mode list * @@ -522,6 +694,7 @@ drm_refresh_rate_mHz(const drmModeModeInfo *info) static struct drm_mode * drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) { + struct drm_head *head = to_drm_head(weston_output_get_first_head(&output->base)); struct drm_mode *mode; mode = malloc(sizeof *mode); @@ -536,6 +709,16 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) mode->mode_info = *info; mode->blob_id = 0; + mode->vic = match_cta_mode(info); + + mode->can_yuv420 = false; + mode->must_yuv420 = false; + if (mode->vic && head->can_yuv420_vic_map[mode->vic]) + mode->can_yuv420 = true; + + if (mode->vic && head->must_yuv420_vic_map[mode->vic]) + mode->must_yuv420 = false; + if (info->type & DRM_MODE_TYPE_PREFERRED) mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; @@ -580,17 +763,31 @@ drm_output_print_modes(struct drm_output *output) const char *aspect_ratio; wl_list_for_each(m, &output->base.mode_list, link) { + const char *yuv_cap_str; + dm = to_drm_mode(m); aspect_ratio = aspect_ratio_to_string(m->aspect_ratio); - weston_log_continue(STAMP_SPACE "%s@%.1f%s%s%s, %.1f MHz\n", + if (dm->vic) + weston_log_continue(STAMP_SPACE "VIC %3d, ", dm->vic); + else + weston_log_continue(STAMP_SPACE " "); + + yuv_cap_str = NULL; + if (dm->must_yuv420) + yuv_cap_str = ", YUV420 only"; + else if (dm->can_yuv420) + yuv_cap_str = ", YUV420 optional"; + + weston_log_continue("%s@%.1f%s%s%s, %.1f MHz%s\n", dm->mode_info.name, 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); + dm->mode_info.clock / 1000.0, + yuv_cap_str ?: ""); } } @@ -721,6 +918,10 @@ update_head_from_connector(struct drm_head *head) vrr_mode_mask = WESTON_VRR_MODE_GAME; weston_head_set_supported_vrr_modes_mask(&head->base, vrr_mode_mask); + head->yuv420_vics = dhi.yuv420_vics; + memcpy(&head->can_yuv420_vic_map, &dhi.can_yuv420_vic_map, sizeof(head->can_yuv420_vic_map)); + memcpy(&head->must_yuv420_vic_map, &dhi.must_yuv420_vic_map, sizeof(head->must_yuv420_vic_map)); + drm_head_info_fini(&dhi); }