SchOS/kernel/dev/gfx.c
Nexoniarz c84d06c5b1 gfx: one glyph blitter, and a terminal cell is a surface
Gate 0's last item, "delete the cell grid" -- done as far as it should be, and
the rest of it retired, which is the part worth explaining.

include/text.h made the case against the grid in four charges: it cannot do
variable-width glyphs, cannot position text off a multiple of eight, cannot
anti-alias, and cannot hold a codepoint above 255. Every one of those was
about the *drawing layer*, and every one is now false -- glyphs carry metrics,
PSOGM positions text at arbitrary pixels, the TrueType path blends 8-bit
coverage, and vtcell.ch is a uint32.

What is left is a grid in vt.c, and deleting that would not modernise the
console; it would stop it being a terminal. VT100 escapes address columns,
TIOCGWINSZ reports rows and columns, and every program that draws a box
assumes both. So the item is checked with its remainder rewritten to say what
was actually wrong, rather than ticked as if the original wording had been
carried out.

**What was actually wrong was the duplicate.** gfx.c walked font_data and
blitted glyphs its own way; text.c did the same job into a struct surface, for
no caller but a sysrq self-test. Two implementations of bearings, coverage and
clipping, which had to agree about a cell size only one of them was told had
changed -- and they did not agree, which is why every line of PSOGM once drew
the next character's bitmap underneath itself.

There is one now, and the mechanism is the pleasing part: the console hands
text_draw_glyph() **the cell as a surface of its own** -- same pixels, same
stride, origin moved and bounds set to that cell. Clipping a tall ascender to
its line is then the same bounds check that clips to the edge of a screen, and
there is no second notion of clipping in the tree. `surface_sub()` is six
lines and replaced fourteen of hand-written comparisons.

The bitmap is reached through font_builtin() like any other provider, so the
early console and the real one are one path with different metrics, and the
`if (!cfont)` branch that walked the array is gone. gfx.c no longer includes
font.h; text.c does, being the only file left that reads it -- which also
retires the note in text.c explaining why it could not.

struct surface gained a bpp, because gfx_draw_pixel handles 16, 24 and 32 and
whatever replaces it has to as well. Zero means 32: a UEFI GOP has no other
format and the loader publishes 32 unconditionally, so callers that do not
care say nothing.

One behaviour change, and it is the documented one: a codepoint the font
cannot draw is now a substituted glyph rather than a blank cell. text.h argues
for that directly -- "a missing glyph that draws nothing makes text silently
shorter and the fault impossible to see".

Verified by screendump: the console renders correctly, PSOGM renders
correctly, and `echo t > /proc/sysrq` still draws its specimen through the
surface path with the back buffer at 32bpp. Both supported configurations: one
boot each, no traps, no panics, socktest and memfdtest green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 17:57:16 +02:00

717 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 "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, the
// font is the built-in bitmap reached through font_builtin(), and 8x16 is what
// that one measures.
//
// This file no longer touches `font_data` at all. It used to walk the array
// itself when there was no TrueType face, which made two glyph blitters that
// had to agree about a cell size only one of them was told had changed -- and
// they did not agree: every line of PSOGM drew five rows of the next
// character's bitmap underneath itself. The bitmap is a font provider like any
// other now, and there is one blitter.
#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. Null only until the first glyph is drawn, which fills it in
// with the built-in bitmap; gfx_font_init() replaces that with a real face
// when there is one.
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)
{
struct surface screen, cell;
if (!fb)
return;
if (x < 0 || y < 0 ||
x + cell_w > (int)scanout.width || y + cell_h > (int)scanout.height)
return;
// There is always a font. Before gfx_font_init() runs it is the built-in
// bitmap, *as a font provider* -- which is the whole point of the provider
// seam: the early console and the real one are the same code path drawing
// through different metrics, rather than two blitters that have to be kept
// in agreement. They were not kept in agreement, and PSOGM printed every
// line twice for it.
if (!cfont) {
cfont = font_builtin();
cfont_baseline = cfont->ops->baseline(cfont);
}
// Background first. 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);
if (cp == ' ')
return; // nothing to draw over the background
// The cell as a surface of its own, so the renderer's own bounds check is
// what stops a tall glyph bleeding into the line above or below. The pen is
// at the cell's left edge, on its baseline.
screen.pixels = (void *)fb;
screen.stride = scanout.stride;
screen.width = (int)scanout.width;
screen.height = (int)scanout.height;
screen.bpp = (int)scanout.bpp;
cell = surface_sub(&screen, x, y, cell_w, cell_h);
text_draw_glyph(&cell, cfont, 0, cfont_baseline, cp, fg);
}
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);
}