From 36e95611ec10e40ad99fae083c18d6ee8580a2f9 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 14 Apr 2026 15:42:18 -0500 Subject: [PATCH 1/3] frontend: Properly print available color formats These should be the short names used in parsing, not the full text of what they represent. Signed-off-by: Derek Foreman --- frontend/main.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/main.c b/frontend/main.c index cfcd83083..08aafe300 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -2420,16 +2420,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; } From 3b33d4fab7bea31121b65f361b78984e24b96392 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 9 Apr 2026 15:00:15 -0500 Subject: [PATCH 2/3] drm: Track VIC for modes Some modes are "standard", and have a VIC (Video Identification Code). Try to figure out if the modes available to us have an associated VIC, store it and print it. This is preparation for eventually enabling YUV420 support, which is only supported on a per-mode basis. Signed-off-by: Derek Foreman --- libweston/backend-drm/drm-internal.h | 1 + libweston/backend-drm/modes.c | 109 ++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index d4ba085d0..e040c05cc 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -293,6 +293,7 @@ struct drm_mode { struct weston_mode base; drmModeModeInfo mode_info; uint32_t blob_id; + uint8_t vic; }; enum drm_fb_type { diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index ab0ce11f1..472b2469d 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -100,6 +100,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) { @@ -509,6 +530,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 * @@ -536,6 +636,8 @@ 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); + if (info->type & DRM_MODE_TYPE_PREFERRED) mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; @@ -583,7 +685,12 @@ drm_output_print_modes(struct drm_output *output) 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 " "); + + weston_log_continue("%s@%.1f%s%s%s, %.1f MHz\n", dm->mode_info.name, m->refresh / 1000.0, aspect_ratio, m->flags & WL_OUTPUT_MODE_PREFERRED ? From d9238bca6a921fc83573f6cc698b7c1f8ed4a825 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Tue, 14 Apr 2026 15:55:28 -0500 Subject: [PATCH 3/3] drm: Look for YUV420 capabilities in the mode list For now, we tag modes with their YUV capabilities manually here, and print that. Signed-off-by: Derek Foreman --- libweston/backend-drm/drm-internal.h | 6 ++ libweston/backend-drm/modes.c | 140 ++++++++++++++++++++++----- 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index e040c05cc..99c25e612 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -294,6 +294,8 @@ struct drm_mode { drmModeModeInfo mode_info; uint32_t blob_id; uint8_t vic; + bool can_yuv420; + bool must_yuv420; }; enum drm_fb_type { @@ -577,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 472b2469d..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 @@ -309,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 = @@ -364,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; } @@ -395,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); @@ -404,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; } @@ -622,6 +694,7 @@ match_cta_mode(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); @@ -638,6 +711,14 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) 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; @@ -682,6 +763,8 @@ 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); @@ -690,14 +773,21 @@ drm_output_print_modes(struct drm_output *output) else weston_log_continue(STAMP_SPACE " "); - weston_log_continue("%s@%.1f%s%s%s, %.1f MHz\n", + 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 ?: ""); } } @@ -828,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); }