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
This commit is contained in:
Toomas Soome 2021-02-20 10:51:28 +02:00
parent c01da939b0
commit becaac3972
5 changed files with 237 additions and 19 deletions

View File

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

View File

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

View File

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

View File

@ -38,6 +38,8 @@ __FBSDID("$FreeBSD$");
#include <efilib.h>
#include <efiuga.h>
#include <efipciio.h>
#include <Protocol/EdidActive.h>
#include <Protocol/EdidDiscovered.h>
#include <machine/metadata.h>
#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);

View File

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