Merge branch 'YUV420' into 'main'

Draft: Check drm modes for YUV420 capabilities

See merge request wayland/weston!2044
This commit is contained in:
Derek Foreman 2026-05-01 10:25:16 -05:00
commit b52234349e
3 changed files with 236 additions and 29 deletions

View file

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

View file

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

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