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 <derek.foreman@collabora.com>
This commit is contained in:
Derek Foreman 2026-04-14 15:55:28 -05:00
parent 3b33d4fab7
commit d9238bca6a
2 changed files with 123 additions and 23 deletions

View file

@ -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 {

View file

@ -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);
}