From becaac3972f1fde4e3c44516399468ba5ca65c9b Mon Sep 17 00:00:00 2001 From: Toomas Soome Date: Sat, 20 Feb 2021 10:51:28 +0200 Subject: [PATCH] loader: use display pixel density for font autoselection Calculate font size from 16 density independent pixels (dp) by using: size = 16 * ppi/160 * display_factor We are specifying font size 16dp, and assuming 1dp = 160ppi. Also apply scaling factor 2 (display_factor). MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D28849 --- stand/common/gfx_fb.c | 110 +++++++++++++++++++++++++++++++++ stand/common/gfx_fb.h | 2 + stand/efi/libefi/efi_console.c | 23 +++++-- stand/efi/loader/framebuffer.c | 96 ++++++++++++++++++++++++++-- stand/i386/libi386/vbe.c | 25 +++++--- 5 files changed, 237 insertions(+), 19 deletions(-) diff --git a/stand/common/gfx_fb.c b/stand/common/gfx_fb.c index 02a0a3d2be22..77cf1d39854f 100644 --- a/stand/common/gfx_fb.c +++ b/stand/common/gfx_fb.c @@ -1863,6 +1863,113 @@ reset_font_flags(void) } } +/* Return w^2 + h^2 or 0, if the dimensions are unknown */ +static unsigned +edid_diagonal_squared(void) +{ + unsigned w, h; + + if (edid_info == NULL) + return (0); + + w = edid_info->display.max_horizontal_image_size; + h = edid_info->display.max_vertical_image_size; + + /* If either one is 0, we have aspect ratio, not size */ + if (w == 0 || h == 0) + return (0); + + /* + * some monitors encode the aspect ratio instead of the physical size. + */ + if ((w == 16 && h == 9) || (w == 16 && h == 10) || + (w == 4 && h == 3) || (w == 5 && h == 4)) + return (0); + + /* + * translate cm to inch, note we scale by 100 here. + */ + w = w * 100 / 254; + h = h * 100 / 254; + + /* Return w^2 + h^2 */ + return (w * w + h * h); +} + +/* + * calculate pixels per inch. + */ +static unsigned +gfx_get_ppi(void) +{ + unsigned dp, di; + + di = edid_diagonal_squared(); + if (di == 0) + return (0); + + dp = gfx_state.tg_fb.fb_width * + gfx_state.tg_fb.fb_width + + gfx_state.tg_fb.fb_height * + gfx_state.tg_fb.fb_height; + + return (isqrt(dp / di)); +} + +/* + * Calculate font size from density independent pixels (dp): + * ((16dp * ppi) / 160) * display_factor. + * Here we are using fixed constants: 1dp == 160 ppi and + * display_factor 2. + * + * We are rounding font size up and are searching for font which is + * not smaller than calculated size value. + */ +static vt_font_bitmap_data_t * +gfx_get_font(void) +{ + unsigned ppi, size; + vt_font_bitmap_data_t *font = NULL; + struct fontlist *fl, *next; + + /* Text mode is not supported here. */ + if (gfx_state.tg_fb_type == FB_TEXT) + return (NULL); + + ppi = gfx_get_ppi(); + if (ppi == 0) + return (NULL); + + /* + * We will search for 16dp font. + * We are using scale up by 10 for roundup. + */ + size = (16 * ppi * 10) / 160; + /* Apply display factor 2. */ + size = roundup(size * 2, 10) / 10; + + STAILQ_FOREACH(fl, &fonts, font_next) { + next = STAILQ_NEXT(fl, font_next); + + /* + * If this is last font or, if next font is smaller, + * we have our font. Make sure, it actually is loaded. + */ + if (next == NULL || next->font_data->vfbd_height < size) { + font = fl->font_data; + if (font->vfbd_font == NULL || + fl->font_flags == FONT_RELOAD) { + if (fl->font_load != NULL && + fl->font_name != NULL) + font = fl->font_load(fl->font_name); + } + break; + } + } + + return (font); +} + static vt_font_bitmap_data_t * set_font(teken_unit_t *rows, teken_unit_t *cols, teken_unit_t h, teken_unit_t w) { @@ -1887,6 +1994,9 @@ set_font(teken_unit_t *rows, teken_unit_t *cols, teken_unit_t h, teken_unit_t w) } } + if (font == NULL) + font = gfx_get_font(); + if (font != NULL) { *rows = height / font->vfbd_height; *cols = width / font->vfbd_width; diff --git a/stand/common/gfx_fb.h b/stand/common/gfx_fb.h index 04076a2c6d38..ac63d7939cef 100644 --- a/stand/common/gfx_fb.h +++ b/stand/common/gfx_fb.h @@ -109,6 +109,8 @@ struct vesa_edid_info { uint8_t checksum; } __packed; +extern struct vesa_edid_info *edid_info; + #define STD_TIMINGS 8 #define DET_TIMINGS 4 diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c index 3cbd121c41da..0c40b362f276 100644 --- a/stand/efi/libefi/efi_console.c +++ b/stand/efi/libefi/efi_console.c @@ -952,13 +952,24 @@ cons_update_mode(bool use_gfx_mode) /* * setup_font() can adjust terminal size. - * Note, we do use UEFI terminal dimensions first, - * this is because the font selection will attempt - * to achieve at least this terminal dimension and - * we do not end up with too small font. + * We can see two kind of bad happening. + * We either can get too small console font - requested + * terminal size is large, display resolution is + * large, and we get very small font. + * Or, we can get too large font - requested + * terminal size is small and this will cause large + * font to be selected. + * Now, the setup_font() is updated to consider + * display density and this should give us mostly + * acceptable font. However, the catch is, not all + * display devices will give us display density. + * Still, we do hope, external monitors do - this is + * where the display size will matter the most. + * And for laptop screens, we should still get good + * results by requesting 80x25 terminal. */ - gfx_state.tg_tp.tp_row = rows; - gfx_state.tg_tp.tp_col = cols; + gfx_state.tg_tp.tp_row = 25; + gfx_state.tg_tp.tp_col = 80; setup_font(&gfx_state, fb_height, fb_width); rows = gfx_state.tg_tp.tp_row; cols = gfx_state.tg_tp.tp_col; diff --git a/stand/efi/loader/framebuffer.c b/stand/efi/loader/framebuffer.c index 509c41844dcb..adb9dfb62cee 100644 --- a/stand/efi/loader/framebuffer.c +++ b/stand/efi/loader/framebuffer.c @@ -38,6 +38,8 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include +#include #include #include "bootstrap.h" @@ -47,6 +49,12 @@ static EFI_GUID conout_guid = EFI_CONSOLE_OUT_DEVICE_GUID; EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; static EFI_GUID pciio_guid = EFI_PCI_IO_PROTOCOL_GUID; static EFI_GUID uga_guid = EFI_UGA_DRAW_PROTOCOL_GUID; +static EFI_GUID active_edid_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; +static EFI_GUID discovered_edid_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID; +static EFI_HANDLE gop_handle; + +/* Cached EDID. */ +struct vesa_edid_info *edid_info = NULL; static EFI_GRAPHICS_OUTPUT *gop; static EFI_UGA_DRAW_PROTOCOL *uga; @@ -467,10 +475,71 @@ efifb_from_uga(struct efi_fb *efifb) return (0); } +/* + * Fetch EDID info. Caller must free the buffer. + */ +static struct vesa_edid_info * +efifb_gop_get_edid(EFI_HANDLE h) +{ + const uint8_t magic[] = EDID_MAGIC; + EFI_EDID_ACTIVE_PROTOCOL *edid; + struct vesa_edid_info *edid_infop; + EFI_GUID *guid; + EFI_STATUS status; + size_t size; + + guid = &active_edid_guid; + status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (status != EFI_SUCCESS || + edid->SizeOfEdid == 0) { + guid = &discovered_edid_guid; + status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (status != EFI_SUCCESS || + edid->SizeOfEdid == 0) + return (NULL); + } + + size = MAX(sizeof(*edid_infop), edid->SizeOfEdid); + + edid_infop = calloc(1, size); + if (edid_infop == NULL) + return (NULL); + + memcpy(edid_infop, edid->Edid, edid->SizeOfEdid); + + /* Validate EDID */ + if (memcmp(edid_infop, magic, sizeof (magic)) != 0) + goto error; + + if (edid_infop->header.version != 1) + goto error; + + return (edid_infop); +error: + free(edid_infop); + return (NULL); +} + +static bool +efifb_get_edid(edid_res_list_t *res) +{ + bool rv = false; + + if (edid_info == NULL) + edid_info = efifb_gop_get_edid(gop_handle); + + if (edid_info != NULL) + rv = gfx_get_edid_resolution(edid_info, res); + + return (rv); +} + int efi_find_framebuffer(teken_gfx_t *gfx_state) { - EFI_HANDLE h, *hlist; + EFI_HANDLE *hlist; UINTN nhandles, i, hsize; struct efi_fb efifb; EFI_STATUS status; @@ -498,23 +567,25 @@ efi_find_framebuffer(teken_gfx_t *gfx_state) /* * Search for ConOut protocol, if not found, use first handle. */ - h = *hlist; + gop_handle = *hlist; for (i = 0; i < nhandles; i++) { void *dummy = NULL; status = OpenProtocolByHandle(hlist[i], &conout_guid, &dummy); if (status == EFI_SUCCESS) { - h = hlist[i]; + gop_handle = hlist[i]; break; } } - status = OpenProtocolByHandle(h, &gop_guid, (void **)&gop); + status = OpenProtocolByHandle(gop_handle, &gop_guid, (void **)&gop); free(hlist); if (status == EFI_SUCCESS) { gfx_state->tg_fb_type = FB_GOP; gfx_state->tg_private = gop; + if (edid_info == NULL) + edid_info = efifb_gop_get_edid(gop_handle); } else { status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga); if (status == EFI_SUCCESS) { @@ -767,9 +838,25 @@ command_gop(int argc, char *argv[]) } else if (strcmp(argv[1], "off") == 0) { (void) cons_update_mode(false); } else if (strcmp(argv[1], "get") == 0) { + edid_res_list_t res; + if (argc != 2) goto usage; + TAILQ_INIT(&res); efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info); + if (efifb_get_edid(&res)) { + struct resolution *rp; + + printf("EDID"); + while ((rp = TAILQ_FIRST(&res)) != NULL) { + printf(" %dx%d", rp->width, rp->height); + TAILQ_REMOVE(&res, rp, next); + free(rp); + } + printf("\n"); + } else { + printf("no EDID information\n"); + } print_efifb(gop->Mode->Mode, &efifb, 1); printf("\n"); } else if (!strcmp(argv[1], "list")) { @@ -778,6 +865,7 @@ command_gop(int argc, char *argv[]) if (argc != 2) goto usage; + pager_open(); for (mode = 0; mode < gop->Mode->MaxMode; mode++) { status = gop->QueryMode(gop, mode, &infosz, &info); diff --git a/stand/i386/libi386/vbe.c b/stand/i386/libi386/vbe.c index 0e9f6929ac05..ef4daffa8380 100644 --- a/stand/i386/libi386/vbe.c +++ b/stand/i386/libi386/vbe.c @@ -51,6 +51,7 @@ static struct modeinfoblock *vbe_mode; static uint16_t *vbe_mode_list; static size_t vbe_mode_list_size; +struct vesa_edid_info *edid_info = NULL; /* The default VGA color palette format is 6 bits per primary color. */ int palette_format = 6; @@ -836,34 +837,40 @@ vbe_dump_mode(int modenum, struct modeinfoblock *mi) static bool vbe_get_edid(edid_res_list_t *res) { - struct vesa_edid_info *edid_info; + struct vesa_edid_info *edidp; const uint8_t magic[] = EDID_MAGIC; int ddc_caps; bool ret = false; + if (edid_info != NULL) + return (gfx_get_edid_resolution(edid_info, res)); + ddc_caps = biosvbe_ddc_caps(); if (ddc_caps == 0) { return (ret); } - edid_info = bio_alloc(sizeof (*edid_info)); - if (edid_info == NULL) + edidp = bio_alloc(sizeof(*edidp)); + if (edidp == NULL) return (ret); - memset(edid_info, 0, sizeof (*edid_info)); + memset(edidp, 0, sizeof(*edidp)); - if (VBE_ERROR(biosvbe_ddc_read_edid(0, edid_info))) + if (VBE_ERROR(biosvbe_ddc_read_edid(0, edidp))) goto done; - if (memcmp(edid_info, magic, sizeof (magic)) != 0) + if (memcmp(edidp, magic, sizeof(magic)) != 0) goto done; /* Unknown EDID version. */ - if (edid_info->header.version != 1) + if (edidp->header.version != 1) goto done; - ret = gfx_get_edid_resolution(edid_info, res); + ret = gfx_get_edid_resolution(edidp, res); + edid_info = malloc(sizeof(*edid_info)); + if (edid_info != NULL) + memcpy(edid_info, edidp, sizeof (*edid_info)); done: - bio_free(edid_info, sizeof (*edid_info)); + bio_free(edidp, sizeof(*edidp)); return (ret); }