"Ą Ę Ś Ł Ż" drew as "?????" and the obvious diagnosis -- the font has no glyphs for them -- was wrong twice over. `struct vtcell.ch` was **one byte** holding a CP437 index, and the console mapped UTF-8 down to it on the way in. U+0104 does not fit in a byte. No font could have fixed that; the cell had to get wider first. So: `ch` is a uint32 codepoint, the mapping on the way in is gone, and the cell draws through kernel/dev/ttf.c against JetBrains Mono linked into the image. Cell size comes from the font's measured advance instead of a hardcoded 8x16. The grid costs 864 KB of BSS instead of 432 KB, which is the honest price of holding a codepoint per cell for six terminals at 240x75. The rasteriser needed three fixes before it could be trusted with this. **21 KB of locals on a 4 KB kernel stack.** `ttf_render` held an outline (512 points as three arrays) and a raster (1024 edges) as automatics. KSTACKSIZE is 4096. They walked off the bottom of the stack into whatever was underneath, which surfaced as user processes faulting on addresses they never touched and then a panic inside freevm() on an overwritten page directory. Nothing pointed anywhere near a font. **And then again, recursively.** load_outline() held a 4736-byte outline as a local *and recursed* for composite glyphs. Composite glyphs are exactly Ą, Ę, Ś, Ł, Ż -- so the second crash arrived the moment the first fix let Unicode reach the screen, looking like a fresh bug. Now a static outline per recursion level with a depth cap of 4 (the font's deepest real nesting is 2), and the leaf parse split into load_simple() so its 512-byte flag array is not multiplied by the depth. Worst chain measured from the object file: ~2.3 KB. Neither showed in tools/ttftest, which has an 8 MB stack. That is the standing hazard of one source file built for host and kernel: the host is most forgiving about exactly the resource the kernel is strictest about. **Component transforms were being skipped**, on the stated theory that a wrong transform is worse than none. It is not. `‘` in this font is the comma glyph with a scale of -1 -- a 180-degree rotation -- and the offset that ships with it only lands correctly after the rotation. Without it the mark went twelve pixels into a ten-pixel cell and was clipped away entirely, so the character read as missing from the font rather than as misplaced. F2Dot14 2x2 now, with SCALED_COMPONENT_OFFSET honoured. The glyph cache was ASCII-only, so every accented letter re-rasterised on every repaint: a screenful of Polish did hundreds of bezier flattenings per scroll where the same screenful of English did none. 95 reserved ASCII slots plus 224 shared slots keyed by codepoint, 64 KB pool. Verified 13680 cached renders byte-identical to direct ones across collisions and a pool flush. tools/ttftest speaks UTF-8 now. Iterating bytes meant it could only ever ask for ASCII, and ASCII is entirely simple glyphs -- it could not reach the code path that mattered. Also: resolvconf's success line moves behind /proc/bootmode, same rule as vgpud. A quiet boot opened with "resolvconf: nameserver 10.0.2.3" where the one line was supposed to be "Welcome to SchOS!". Failures stay loud. Verified on screen, not just on the host: Ą Ę Ś Ł Ż ć ń ó ź, Ω λ π Œ Ð Æ ß, ∞ ‘x’ → ≤ all render, panic=0 across four boots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
635 lines
20 KiB
C
635 lines
20 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 "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.
|
|
static int cell_w = 8;
|
|
static int cell_h = 16;
|
|
|
|
#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);
|
|
|
|
// 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;
|
|
|
|
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));
|
|
|
|
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)
|
|
{
|
|
// No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and
|
|
// a uchar cannot index past them.
|
|
if (!fb) return;
|
|
|
|
for (int row = 0; row < CHAR_HEIGHT; row++) {
|
|
uchar row_data = font_data[c][row];
|
|
for (int col = 0; col < CHAR_WIDTH; col++) {
|
|
if (row_data & (0x80 >> col)) {
|
|
gfx_draw_pixel(x + col, y + row, color);
|
|
} else {
|
|
gfx_draw_pixel(x + col, y + row, 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.
|
|
void
|
|
gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg)
|
|
{
|
|
if (!fb)
|
|
return;
|
|
|
|
int x = col * cell_w, y = row * cell_h;
|
|
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.
|
|
if (!cfont) {
|
|
uchar g = cp < 256 ? (uchar)cp : ' ';
|
|
for (int r = 0; r < cell_h; r++) {
|
|
uchar bits = font_data[g][r];
|
|
for (int c = 0; c < cell_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));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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[];
|
|
uint32 len = (uint32)(_binary_consfont_end - _binary_consfont_start);
|
|
struct glyph gl;
|
|
|
|
if (cfont)
|
|
return;
|
|
// 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, _binary_consfont_start, 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);
|
|
|
|
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;
|
|
|
|
// 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);
|
|
}
|