Gate 0's last-but-one open item. Drop a TrueType file at \schos\console.ttf on
the ESP and the console is drawn with it; with no file, the face linked into
the kernel image, which is what an early panic needs and is not a degraded
mode.
**The loader reads it, and that is the decision worth writing down.** The
kernel opening the file once the root filesystem is mounted is the obvious
design. It is also wrong: the console has been drawing since the kernel's
first line, so a font arriving later changes the cell size, which changes the
column count, which re-lays out every terminal -- and blanks them. The kernel
log would vanish off the screen at the moment the root filesystem mounted,
which is exactly the log a machine being brought up for the first time is
being read for. Since the next thing on the list is real hardware, losing that
was not a trade worth making.
So the choice happens before anything is drawn. Three details make it cheap:
- **EfiReservedMemoryType**, not EfiLoaderData. The E820 handoff turns
loader data into usable memory, on the argument that the loader's own
pages are the kernel's the moment it runs -- true of the loader, not true
of a buffer it wants the kernel to keep reading from.
- **Below 4 GB**, because the kernel reaches this through its direct map and
that is how far the direct map goes. A font above it would need an ioremap
the console cannot do before there is a console.
- **Used in place.** No copy, which matters because the kernel has no
contiguous allocator to copy 112 KB into -- the only one is the DMA arena,
and that belongs to devices.
Sized without EFI_FILE_INFO and its GUID: seek past the end and ask where that
landed, which the specification requires to clamp.
Every failure falls back and keeps going -- no file, implausible size, no
memory, short read, unparseable file. The last says so in the log, because a
font that silently did not load looks exactly like a font that did.
Verified by booting with JetBrainsMono-ExtraBold.ttf on the ESP and
screendumping the console: visibly the bold face, against the Regular of a
boot with no file. Three boots each way, no traps and no panics, socktest and
memfdtest green in all six.
Nothing ships a console.ttf: the file would be the file already in the kernel,
and two copies of a font are two things that can disagree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
729 lines
24 KiB
C
729 lines
24 KiB
C
// Drawing and the text console, on whatever display is bound. See include/gfx.h
|
|
// for why this is not a driver and does not name one.
|
|
//
|
|
// It was kernel/dev/vga.c, and the name was the problem: panic.c called
|
|
// gfx_fill_rect, console.c called gfx_putc, /dev/fb0 called gfx_fb_phys. None
|
|
// of this code is VGA-specific -- it is bitmap drawing over a linear
|
|
// framebuffer -- and the one part that genuinely was is now the driver, in
|
|
// kernel/drivers/gpu/vga/.
|
|
//
|
|
// The framebuffer arrives through gfx_attach_scanout() rather than being read
|
|
// out of the boot block here. That is the whole of the inversion: a driver
|
|
// publishes what it owns, and this file draws on it without knowing what it is.
|
|
|
|
#include "types.h"
|
|
#include "x86.h"
|
|
#include "memlayout.h"
|
|
#include "defs.h"
|
|
#include "theme.h"
|
|
#include "font.h"
|
|
#include "logo.h"
|
|
#include "device.h"
|
|
#include "display.h"
|
|
#include "gfx.h"
|
|
#include "vt.h"
|
|
#include "text.h"
|
|
#include "schboot.h"
|
|
#include "schboot.h"
|
|
#include "memlayout.h"
|
|
#include "ttf.h"
|
|
|
|
#define BACKSPACE 0x100
|
|
#define COLOR_BG SCH_BG
|
|
|
|
// The cell size, and why it is not a constant any more.
|
|
//
|
|
// It was 8x16, the size of the built-in bitmap font, and everything in this
|
|
// file multiplied by it. That font is CP437: 256 glyphs, no Ą, no Ę, no Ś, no
|
|
// Ω, no ∞. Those did not come out wrong, they came out as `?`, because the
|
|
// grid could not hold a codepoint above 255 and mapped what it could not keep.
|
|
//
|
|
// So the console draws from a real typeface now, and a real typeface decides
|
|
// its own metrics. These start at the bitmap's size because the console has to
|
|
// work from the first panic onwards -- before gfx_font_init() has run there is
|
|
// no font, and 8x16 against font_data is exactly what there always was.
|
|
// The bitmap's own geometry, which is fixed forever: font_data is
|
|
// `uchar[256][16]` and each row is eight bits. Naming it separately from the
|
|
// cell size is not pedantry -- the two were the same number, then one of them
|
|
// started moving, and the code that kept indexing font_data by the moving one
|
|
// read straight off the end of every glyph.
|
|
#define BITMAP_W 8
|
|
#define BITMAP_H 16
|
|
|
|
static int cell_w = BITMAP_W;
|
|
static int cell_h = BITMAP_H;
|
|
|
|
#define CHAR_WIDTH cell_w
|
|
#define CHAR_HEIGHT cell_h
|
|
|
|
// The typeface, once there is one. `cfont` is the switch: null means fall back
|
|
// to the bitmap, which is what early boot and any font failure get.
|
|
static struct ttf consttf;
|
|
static struct font *cfont;
|
|
static int cfont_baseline;
|
|
|
|
static uint32 blend(uint32 bg, uint32 fg, uint a);
|
|
static void draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg);
|
|
|
|
// The scanout, as published by whichever driver owns the display. Zeroed until
|
|
// one does, and every entry point checks `fb` before touching memory -- a
|
|
// kernel that panics before any display driver has run still has to be able to
|
|
// call in here and do nothing gracefully.
|
|
static struct fb_info scanout;
|
|
static volatile uchar *fb;
|
|
|
|
// What has been drawn since anyone last asked.
|
|
//
|
|
// The console framebuffer is one screen on a machine that may have two, and
|
|
// the second one is a copy of this that somebody has to make. Copying all of
|
|
// it every time would be four megabytes to move one line of text, so the rows
|
|
// that were touched are remembered as a single span -- not a list of
|
|
// rectangles, because console output is lines and a span is what lines make.
|
|
//
|
|
// Rows rather than pixels: the copy is row-at-a-time anyway, and a column
|
|
// range would save nothing a memcpy cares about.
|
|
static int dmg_y0 = -1, dmg_y1 = -1;
|
|
|
|
static void
|
|
damage(int y0, int y1)
|
|
{
|
|
if (y0 < 0) y0 = 0;
|
|
if (y1 >= (int)scanout.height) y1 = (int)scanout.height - 1;
|
|
if (y1 < y0) return;
|
|
if (dmg_y0 < 0 || y0 < dmg_y0) dmg_y0 = y0;
|
|
if (dmg_y1 < 0 || y1 > dmg_y1) dmg_y1 = y1;
|
|
}
|
|
|
|
// Hand the span over and forget it. Returns 0 when nothing has changed, which
|
|
// is the common case and the reason the caller can afford to ask often.
|
|
int
|
|
gfx_damage_take(int *y0, int *y1)
|
|
{
|
|
if (dmg_y0 < 0)
|
|
return 0;
|
|
*y0 = dmg_y0;
|
|
*y1 = dmg_y1;
|
|
dmg_y0 = dmg_y1 = -1;
|
|
return 1;
|
|
}
|
|
|
|
static int cursor_x = 0;
|
|
static int cursor_y = 0;
|
|
// Current fill color for glyph backgrounds, backspace erase, and scroll --
|
|
// normally COLOR_BG, but panic() switches this to paint the whole console a
|
|
// solid color (e.g. PSOGM's purple) instead of leaving cream-colored boxes
|
|
// around each character.
|
|
static uint32 gfx_bg = COLOR_BG;
|
|
|
|
void
|
|
gfx_attach_scanout(const struct fb_info *info)
|
|
{
|
|
scanout = *info;
|
|
fb = info->ptr;
|
|
|
|
// Tell the terminals how big the screen actually is. They were sized before
|
|
// this driver existed -- the PCI bus is walked long after the terminals are
|
|
// set up -- so until this call every grid is the 80x24 fallback. See
|
|
// vt_resize().
|
|
vt_resize();
|
|
}
|
|
|
|
const struct fb_info *
|
|
gfx_scanout(void)
|
|
{
|
|
return fb ? &scanout : 0;
|
|
}
|
|
|
|
void
|
|
gfx_set_bg(uint32 color)
|
|
{
|
|
gfx_bg = color;
|
|
}
|
|
|
|
|
|
// The mode that was actually set. Exported because the resolution is chosen at
|
|
// boot now (press R -- see boot/bios/stage2.S), so nothing above may assume it.
|
|
uint32
|
|
gfx_width(void)
|
|
{
|
|
return scanout.width;
|
|
}
|
|
|
|
uint32
|
|
gfx_height(void)
|
|
{
|
|
return scanout.height;
|
|
}
|
|
|
|
void
|
|
gfx_draw_pixel(int x, int y, uint32 color)
|
|
{
|
|
if (!fb || x < 0 || x >= scanout.width || y < 0 || y >= scanout.height)
|
|
return;
|
|
|
|
uint32 offset = (y * scanout.stride) + (x * (scanout.bpp / 8));
|
|
|
|
damage(y, y);
|
|
|
|
if (scanout.bpp == 32) {
|
|
*(volatile uint32*)(fb + offset) = color;
|
|
} else if (scanout.bpp == 24) {
|
|
fb[offset] = color & 0xFF;
|
|
fb[offset + 1] = (color >> 8) & 0xFF;
|
|
fb[offset + 2] = (color >> 16) & 0xFF;
|
|
} else if (scanout.bpp == 16) {
|
|
uint16 c16 = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F);
|
|
*(volatile uint16*)(fb + offset) = c16;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfx_clear(uint32 color)
|
|
{
|
|
for (int y = 0; y < scanout.height; y++) {
|
|
for (int x = 0; x < scanout.width; x++) {
|
|
gfx_draw_pixel(x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gfx_draw_char(int x, int y, uchar c, uint32 color)
|
|
{
|
|
// Pixel-positioned rather than on the grid -- the crash screen lays itself
|
|
// out in pixels because it is a report, not a terminal. Same glyph path as
|
|
// the grid uses, which it did not used to be; see draw_glyph_px().
|
|
draw_glyph_px(x, y, c, color, gfx_bg);
|
|
}
|
|
|
|
void
|
|
gfx_puts(int x, int y, char *s, uint32 color)
|
|
{
|
|
while (*s) {
|
|
gfx_putc((uchar)*s, color);
|
|
s++;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfx_putc(int c, uint32 color)
|
|
{
|
|
if (!fb) return;
|
|
|
|
if (c == BACKSPACE || c == 0x08 || c == '\b' || c == 127) {
|
|
if (cursor_x >= CHAR_WIDTH) {
|
|
cursor_x -= CHAR_WIDTH;
|
|
for (int i = 0; i < CHAR_HEIGHT; i++) {
|
|
for (int j = 0; j < CHAR_WIDTH; j++) {
|
|
gfx_draw_pixel(cursor_x + j, cursor_y + i, gfx_bg);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (c == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y += CHAR_HEIGHT;
|
|
} else if (c == '\r') {
|
|
cursor_x = 0;
|
|
} else if (c == '\t') {
|
|
// Advance to the next multiple of eight columns, erasing as it goes so a
|
|
// tab overwrites rather than skipping over stale characters.
|
|
//
|
|
// There was no tab case at all, so '\t' fell through to gfx_draw_char and
|
|
// was drawn as glyph 9 -- which in CP437 is a hollow circle. Pressing Tab
|
|
// printed a circle. Same for every other control character: the font has
|
|
// a picture for each of them, and none of those pictures is what the
|
|
// character means.
|
|
int col = cursor_x / CHAR_WIDTH;
|
|
int stop = (col + 8) & ~7;
|
|
while (col < stop && cursor_x < (int)scanout.width) {
|
|
for (int i = 0; i < CHAR_HEIGHT; i++)
|
|
for (int j = 0; j < CHAR_WIDTH; j++)
|
|
gfx_draw_pixel(cursor_x + j, cursor_y + i, gfx_bg);
|
|
cursor_x += CHAR_WIDTH;
|
|
col++;
|
|
}
|
|
} else if (c < 32) {
|
|
return; // any other control character: not a glyph
|
|
} else {
|
|
gfx_draw_char(cursor_x, cursor_y, (uchar)c, color);
|
|
cursor_x += CHAR_WIDTH;
|
|
}
|
|
|
|
if (cursor_x >= scanout.width) {
|
|
cursor_x = 0;
|
|
cursor_y += CHAR_HEIGHT;
|
|
}
|
|
|
|
if (cursor_y > (scanout.height - CHAR_HEIGHT)) {
|
|
uint32 line_size = scanout.stride * CHAR_HEIGHT;
|
|
uint32 total_size = scanout.stride * scanout.height;
|
|
uint32 copy_size = total_size - line_size;
|
|
|
|
memmove((void*)fb, (void*)(fb + line_size), copy_size);
|
|
|
|
for (int y = scanout.height - CHAR_HEIGHT; y < scanout.height; y++) {
|
|
for (int x = 0; x < scanout.width; x++) {
|
|
gfx_draw_pixel(x, y, gfx_bg);
|
|
}
|
|
}
|
|
cursor_y = scanout.height - CHAR_HEIGHT;
|
|
}
|
|
}
|
|
|
|
// --- primitives for the text layer ----------------------------------------
|
|
//
|
|
// kernel/dev/vt.c owns a grid of cells and needs exactly two things the
|
|
// console-cursor interface above cannot give it: draw one cell at a grid
|
|
// position with its *own* background, and scroll the whole screen without
|
|
// consulting a cursor. Everything else a terminal does is bookkeeping on the
|
|
// grid, which is not this file's business.
|
|
|
|
// One cell, addressed in cells rather than pixels, with its own colours.
|
|
// gfx_draw_char paints its background from the file-static gfx_bg, which is
|
|
// right for a console that has one background and wrong for a terminal where
|
|
// every cell carries its own.
|
|
// One glyph, at a pixel position, in a cell-sized box.
|
|
//
|
|
// Both the console grid and the pixel-positioned text in the crash screen come
|
|
// through here, which is the point: they used to be two implementations, and
|
|
// only one of them was updated when the cell size stopped being 8x16.
|
|
// gfx_draw_char() still walked `font_data[c][row]` for `row < CHAR_HEIGHT` --
|
|
// and CHAR_HEIGHT is the *TrueType* cell height now, while font_data has
|
|
// exactly 16 rows per glyph. Every line of PSOGM therefore drew five rows of
|
|
// the *next character's* bitmap underneath itself. It looked like each line
|
|
// had been printed twice.
|
|
static void
|
|
draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg)
|
|
{
|
|
if (!fb)
|
|
return;
|
|
if (x < 0 || y < 0 ||
|
|
x + cell_w > (int)scanout.width || y + cell_h > (int)scanout.height)
|
|
return;
|
|
|
|
// No font yet: the bitmap, exactly as before. Anything it cannot hold is
|
|
// drawn as a space rather than as the wrong glyph -- `?` was at least honest
|
|
// about being a substitute, and a CP437 picture of something unrelated is
|
|
// not.
|
|
//
|
|
// BITMAP_W/H and not cell_w/h: the bitmap's geometry is a property of
|
|
// font_data, not of whatever the console decided a cell is. They agree
|
|
// whenever cfont is null, and the assert below is what keeps that true.
|
|
if (!cfont) {
|
|
uchar g = cp < 256 ? (uchar)cp : ' ';
|
|
for (int r = 0; r < BITMAP_H; r++) {
|
|
uchar bits = font_data[g][r];
|
|
for (int c = 0; c < BITMAP_W; c++)
|
|
gfx_draw_pixel(x + c, y + r, (bits & (0x80 >> c)) ? fg : bg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Background first, then the glyph blended over it. The cell is always
|
|
// filled: a terminal cell owns its rectangle, and leaving the old pixels
|
|
// where a narrow glyph does not reach is how ghosting gets in.
|
|
for (int r = 0; r < cell_h; r++)
|
|
for (int c = 0; c < cell_w; c++)
|
|
gfx_draw_pixel(x + c, y + r, bg);
|
|
|
|
struct glyph gl;
|
|
if (cp == ' ' || cfont->ops->glyph(cfont, cp, &gl) != 0)
|
|
return; // no such glyph: the cell stays background
|
|
|
|
// `cov` is w*h bytes of coverage, 0..255, positioned by the bearing from the
|
|
// pen. The pen sits on the baseline at the left of the cell.
|
|
int gx = x + gl.bearing_x;
|
|
int gy = y + cfont_baseline - gl.bearing_y;
|
|
|
|
for (int r = 0; r < gl.h; r++) {
|
|
int py = gy + r;
|
|
if (py < y || py >= y + cell_h)
|
|
continue; // clip to the cell: a tall glyph must not
|
|
for (int c = 0; c < gl.w; c++) { // bleed into the line above or below
|
|
int px = gx + c;
|
|
if (px < x || px >= x + cell_w)
|
|
continue;
|
|
uint a = gl.cov[r * gl.w + c];
|
|
if (a == 0)
|
|
continue;
|
|
gfx_draw_pixel(px, py, a == 255 ? fg : blend(bg, fg, a));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
|
|
{
|
|
draw_glyph_px(col * cell_w, row * cell_h, cp, fg, bg);
|
|
}
|
|
|
|
// Bring the console typeface up. Called once, early, from the display path.
|
|
//
|
|
// The font is linked into the kernel (see CONSFONT in the Makefile) because
|
|
// the console has to work before there is a filesystem -- a panic during boot
|
|
// has to be readable.
|
|
//
|
|
// A size failure is not fatal and not silent: `cfont` stays null and every
|
|
// cell keeps coming from the bitmap, which is what this system has always
|
|
// drawn. A console that lost its font would be a much worse outcome than one
|
|
// that never had a good one.
|
|
void
|
|
gfx_font_init(void)
|
|
{
|
|
extern unsigned char _binary_consfont_start[], _binary_consfont_end[];
|
|
const unsigned char *data = _binary_consfont_start;
|
|
uint32 len = (uint32)(_binary_consfont_end - _binary_consfont_start);
|
|
struct glyph gl;
|
|
|
|
if (cfont)
|
|
return;
|
|
|
|
// A typeface off the boot volume, if the loader found one.
|
|
//
|
|
// Tried first and fallen back from silently: the built-in font is not a
|
|
// degraded mode, it is the same font, and a machine with no console.ttf is
|
|
// the ordinary case rather than a fault. What this buys is that the font is
|
|
// *replaceable* -- drop a different TrueType file on the ESP and the console
|
|
// is drawn with it, with no kernel to rebuild.
|
|
//
|
|
// In place, out of memory the loader marked reserved. See SCHBOOT_FONT_ADDR
|
|
// for why the kernel does not read this file itself.
|
|
{
|
|
uint64 font_pa = *(volatile uint64 *)P2V(SCHBOOT_FONT_ADDR);
|
|
uint32 font_len = *(volatile uint32 *)P2V(SCHBOOT_FONT_LEN);
|
|
|
|
if (font_pa && font_len >= 4096 && font_pa + font_len <= 0x100000000ULL) {
|
|
data = (const unsigned char *)P2V(font_pa);
|
|
len = font_len;
|
|
}
|
|
}
|
|
|
|
// 16 pixels per em. The bitmap it replaces is 16 tall, so the terminal keeps
|
|
// the number of rows it had and nothing that assumed a screenful has to
|
|
// change.
|
|
if (ttf_open(&consttf, data, len, 16) != 0) {
|
|
// A bad file on the ESP must not cost the console its font. Nothing has
|
|
// been changed yet at this point, so the built-in one is still there to
|
|
// fall back to -- and it is worth saying, because a font that silently
|
|
// did not load looks exactly like a font that did.
|
|
if (data == _binary_consfont_start)
|
|
return;
|
|
kinfo("console: the font on the boot volume would not parse; "
|
|
"using the built-in one\n");
|
|
data = _binary_consfont_start;
|
|
len = (uint32)(_binary_consfont_end - _binary_consfont_start);
|
|
if (ttf_open(&consttf, data, len, 16) != 0)
|
|
return;
|
|
}
|
|
|
|
struct font *f = ttf_font(&consttf);
|
|
if (!f)
|
|
return;
|
|
|
|
// The cell is as wide as the font's own advance. JetBrains Mono is
|
|
// monospaced, so measuring one glyph measures all of them -- and measuring
|
|
// rather than assuming is what keeps this correct if the font is ever
|
|
// changed for another one.
|
|
if (f->ops->glyph(f, 'M', &gl) != 0 || gl.advance <= 0)
|
|
return;
|
|
|
|
int w = gl.advance, h = f->ops->height(f);
|
|
|
|
// Checked *before* they are stored. Writing them first and returning on a
|
|
// bad value leaves the console with a cell size no font is drawing at, and
|
|
// the bitmap fallback reading past its own 16-row glyphs.
|
|
if (w < 4 || w > 64 || h < 6 || h > 64)
|
|
return; // implausible metrics: keep the bitmap
|
|
|
|
cell_w = w;
|
|
cell_h = h;
|
|
cfont_baseline = f->ops->baseline(f);
|
|
cfont = f;
|
|
}
|
|
|
|
// **Nothing here prints.** gfx_font_init() runs before vt_init(), because
|
|
// vt_init() has to know how big a cell is before it can work out how many fit
|
|
// on the screen -- and by then cons_serial_only may already be clear, so a
|
|
// cprintf() would reach vt_current() before there is a current terminal. That
|
|
// is a fault during boot with no console to report it on, which is a
|
|
// spectacularly unhelpful way to fail.
|
|
//
|
|
// So main() asks afterwards and says it then.
|
|
int
|
|
gfx_font_ready(void)
|
|
{
|
|
return cfont != 0;
|
|
}
|
|
|
|
void
|
|
gfx_cell_size(int *w, int *h)
|
|
{
|
|
if (w) *w = cell_w;
|
|
if (h) *h = cell_h;
|
|
}
|
|
|
|
// Move the whole screen up one text row and clear the row it exposes. The
|
|
// cursor is not touched: the caller tracks it in cells and knows where it
|
|
// wants to be, which is not necessarily where a pixel-scroll would leave it.
|
|
void
|
|
gfx_scroll_up(uint32 bg)
|
|
{
|
|
if (!fb || scanout.height < CHAR_HEIGHT)
|
|
return;
|
|
|
|
uint32 line_size = scanout.stride * CHAR_HEIGHT;
|
|
uint32 total_size = scanout.stride * scanout.height;
|
|
|
|
memmove((void*)fb, (void*)(fb + line_size), total_size - line_size);
|
|
damage(0, (int)scanout.height - 1); // a scroll moves every row
|
|
|
|
for (int y = (int)scanout.height - CHAR_HEIGHT; y < (int)scanout.height; y++)
|
|
for (int x = 0; x < (int)scanout.width; x++)
|
|
gfx_draw_pixel(x, y, bg);
|
|
}
|
|
|
|
void
|
|
gfx_screen_reset(void)
|
|
{
|
|
gfx_bg = COLOR_BG;
|
|
gfx_clear(COLOR_BG);
|
|
cursor_x = 0;
|
|
cursor_y = 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Drawing primitives for the crash screen */
|
|
/* */
|
|
/* The crash screen lays itself out rather than printing, which needs */
|
|
/* two things the console never did: filled shapes, and text at an */
|
|
/* absolute position that leaves the cursor alone. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int
|
|
gfx_ready(void)
|
|
{
|
|
return fb != 0;
|
|
}
|
|
|
|
void
|
|
gfx_dims(int *w, int *h)
|
|
{
|
|
if (w) *w = (int)scanout.width;
|
|
if (h) *h = (int)scanout.height;
|
|
}
|
|
|
|
int gfx_char_w(void) { return CHAR_WIDTH; }
|
|
int gfx_char_h(void) { return CHAR_HEIGHT; }
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Framebuffer geometry */
|
|
/* */
|
|
/* Whatever the attached scanout says, rather than anything read from */
|
|
/* the boot block here -- the driver that owns the display is the only */
|
|
/* thing that knows where the pixels are. See kernel/dev/fb.c for the */
|
|
/* device these back. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
uint64 gfx_fb_phys(void) { return scanout.phys; }
|
|
|
|
// Bytes of framebuffer, measured in scanlines rather than as
|
|
// width * height * bpp/8 -- the two differ whenever the card pads its rows,
|
|
// and it is the stride that says how far apart two rows really are.
|
|
uint64 gfx_fb_size(void)
|
|
{
|
|
if (!fb)
|
|
return 0;
|
|
return (uint64)scanout.stride * (uint64)scanout.height;
|
|
}
|
|
|
|
uint32 gfx_fb_stride(void) { return scanout.stride; }
|
|
uint32 gfx_fb_bpp(void) { return scanout.bpp; }
|
|
|
|
volatile uchar *gfx_fb_ptr(void) { return fb; }
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Cursor addressing and erasing */
|
|
/* */
|
|
/* What an ANSI escape parser needs and gfx_putc alone cannot give it: */
|
|
/* the ability to put the cursor somewhere specific and to erase a */
|
|
/* region without writing spaces over it. See kernel/core/console.c. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int gfx_cols(void) { return scanout.width ? (int)(scanout.width / CHAR_WIDTH) : 0; }
|
|
int gfx_rows(void) { return scanout.height ? (int)(scanout.height / CHAR_HEIGHT) : 0; }
|
|
|
|
void
|
|
gfx_cursor_get(int *col, int *row)
|
|
{
|
|
if (col) *col = cursor_x / CHAR_WIDTH;
|
|
if (row) *row = cursor_y / CHAR_HEIGHT;
|
|
}
|
|
|
|
// Clamped rather than validated: an escape sequence is untrusted input that
|
|
// arrives from any program that can write to the terminal, and "CUP to row
|
|
// 9999" should put the cursor on the last row, not off the end of the
|
|
// framebuffer.
|
|
void
|
|
gfx_cursor_set(int col, int row)
|
|
{
|
|
int maxc = gfx_cols(), maxr = gfx_rows();
|
|
|
|
if (maxc <= 0 || maxr <= 0)
|
|
return;
|
|
if (col < 0) col = 0;
|
|
if (row < 0) row = 0;
|
|
if (col >= maxc) col = maxc - 1;
|
|
if (row >= maxr) row = maxr - 1;
|
|
|
|
cursor_x = col * CHAR_WIDTH;
|
|
cursor_y = row * CHAR_HEIGHT;
|
|
}
|
|
|
|
// Fill a run of character cells with the background colour.
|
|
static void
|
|
erase_cells(int col, int row, int count)
|
|
{
|
|
int maxc = gfx_cols();
|
|
|
|
while (count-- > 0) {
|
|
int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT;
|
|
for (int i = 0; i < CHAR_HEIGHT; i++)
|
|
for (int j = 0; j < CHAR_WIDTH; j++)
|
|
gfx_draw_pixel(x + j, y + i, gfx_bg);
|
|
if (++col >= maxc) { col = 0; row++; }
|
|
}
|
|
}
|
|
|
|
// ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all.
|
|
void
|
|
gfx_erase_display(int mode)
|
|
{
|
|
int col, row, maxc = gfx_cols(), maxr = gfx_rows();
|
|
|
|
gfx_cursor_get(&col, &row);
|
|
switch (mode) {
|
|
case 0: erase_cells(col, row, (maxr - row) * maxc - col); break;
|
|
case 1: erase_cells(0, 0, row * maxc + col + 1); break;
|
|
default: gfx_clear(gfx_bg); break;
|
|
}
|
|
}
|
|
|
|
// EL -- erase in line. Same three modes, bounded to the current row.
|
|
void
|
|
gfx_erase_line(int mode)
|
|
{
|
|
int col, row, maxc = gfx_cols();
|
|
|
|
gfx_cursor_get(&col, &row);
|
|
switch (mode) {
|
|
case 0: erase_cells(col, row, maxc - col); break;
|
|
case 1: erase_cells(0, row, col + 1); break;
|
|
default: erase_cells(0, row, maxc); break;
|
|
}
|
|
}
|
|
|
|
// Row pointer computed once per row rather than per pixel. The crash screen
|
|
// paints a solid card behind the QR symbol, and at 8 pixels per module that is
|
|
// tens of thousands of writes going through gfx_draw_pixel's bounds check and
|
|
// multiply.
|
|
void
|
|
gfx_fill_rect(int x0, int y0, int w, int h, uint32 color)
|
|
{
|
|
if (!fb)
|
|
return;
|
|
if (x0 < 0) { w += x0; x0 = 0; }
|
|
if (y0 < 0) { h += y0; y0 = 0; }
|
|
if (x0 + w > (int)scanout.width) w = (int)scanout.width - x0;
|
|
if (y0 + h > (int)scanout.height) h = (int)scanout.height - y0;
|
|
if (w <= 0 || h <= 0)
|
|
return;
|
|
|
|
damage(y0, y0 + h - 1);
|
|
|
|
// Only the 32bpp case gets the fast path; anything else is rare enough that
|
|
// correctness through gfx_draw_pixel is the better trade.
|
|
if (scanout.bpp != 32) {
|
|
for (int y = y0; y < y0 + h; y++)
|
|
for (int x = x0; x < x0 + w; x++)
|
|
gfx_draw_pixel(x, y, color);
|
|
return;
|
|
}
|
|
for (int y = y0; y < y0 + h; y++) {
|
|
volatile uint32 *row = (volatile uint32 *)(fb + y * scanout.stride) + x0;
|
|
for (int x = 0; x < w; x++)
|
|
row[x] = color;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfx_fill_circle(int cx, int cy, int r, uint32 color)
|
|
{
|
|
for (int dy = -r; dy <= r; dy++)
|
|
for (int dx = -r; dx <= r; dx++)
|
|
if (dx * dx + dy * dy <= r * r)
|
|
gfx_draw_pixel(cx + dx, cy + dy, color);
|
|
}
|
|
|
|
// Text at an absolute position, leaving the console cursor alone. The crash
|
|
// screen lays itself out, so gfx_putc's cursor tracking and scrolling are
|
|
// exactly what it does not want.
|
|
void
|
|
gfx_text_at(int x, int y, const char *s, uint32 color)
|
|
{
|
|
for (int i = 0; s[i]; i++)
|
|
gfx_draw_char(x + i * CHAR_WIDTH, y, (uchar)s[i], color);
|
|
}
|
|
|
|
// --- Logo ------------------------------------------------------------------
|
|
//
|
|
// The boot splash's logo is drawn by the *loader*, not here -- see splash() in
|
|
// boot/bios/stage2main.c. It has to be, because the kernel does not reach gfx_init()
|
|
// until roughly the last 300ms of a 2.8s boot: drawing it here put the logo on
|
|
// screen for one frame before console_takeover() wiped it, which is worse than
|
|
// not having one.
|
|
//
|
|
// What lives here is the same drawing code against the same mask, for the
|
|
// things that want the mark *after* boot -- the crash screen, and eventually
|
|
// whatever schinit shows. Keeping one copy of the compositing logic is why this
|
|
// is not simply deleted.
|
|
|
|
// Blend `fg` over `bg` by coverage a (0..255). Per-channel, integer only: this
|
|
// runs before any floating point is safe to touch.
|
|
static uint32
|
|
blend(uint32 bg, uint32 fg, uint a)
|
|
{
|
|
uint32 r = (((fg >> 16) & 0xFF) * a + ((bg >> 16) & 0xFF) * (255 - a)) / 255;
|
|
uint32 g = (((fg >> 8) & 0xFF) * a + ((bg >> 8) & 0xFF) * (255 - a)) / 255;
|
|
uint32 b = (((fg ) & 0xFF) * a + ((bg ) & 0xFF) * (255 - a)) / 255;
|
|
return (r << 16) | (g << 8) | b;
|
|
}
|
|
|
|
void
|
|
gfx_draw_logo(int x, int y, uint32 color, uint32 bg)
|
|
{
|
|
if (!fb)
|
|
return;
|
|
for (int dy = 0; dy < LOGO_H; dy++) {
|
|
for (int dx = 0; dx < LOGO_W; dx++) {
|
|
uint a = LOGO_INK(dy * LOGO_W + dx);
|
|
// Skip the background rather than painting it. The mask is a square but
|
|
// the mark is a circle, so most of it is empty -- and drawing that empty
|
|
// area in bg would stamp a visible box over whatever is underneath.
|
|
if (a == 0)
|
|
continue;
|
|
gfx_draw_pixel(x + dx, y + dy, a == 255 ? color : blend(bg, color, a));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Centred, for callers that own the whole screen.
|
|
void
|
|
gfx_logo_centered(int cy, uint32 color, uint32 bg)
|
|
{
|
|
if (!fb || scanout.width < LOGO_W)
|
|
return;
|
|
gfx_draw_logo(((int)scanout.width - LOGO_W) / 2, cy - LOGO_H / 2, color, bg);
|
|
}
|