SchOS/kernel/dev/gfx.c
Nexoniarz 2aef583aad Twelve login terminals, root only, console fixes, a faster build, a Niveus splash, and X back on evdev
- tty1-tty12 all run login; the image has only root. /dev/tty7..12 could not
  be written (cdev.c knew minors 10-16 only), which sent their jobs into a
  restart storm that took the supervisor down; fixed, and stat reports them as
  Linux's tty7..12.
- Erasing text at a prompt did nothing: erase_one sent a bare backspace, which
  only moves; it and the echo path now send back-blank-back through vt_feed.
- A white cursor block was left at the end of every line of a verbose boot:
  a line feed at the bottom scrolls the pixels, cursor included, and the
  cursor was erased after the scroll. It comes off first now. A blank cell is
  also marked dirty, which it was not.
- ldlsd died with a general protection fault on real hardware in its variadic
  log function: Linux module code calls with an 8-byte-aligned stack and an
  unset %al, and the prologue's movaps faults. LDLS guest code is built with
  -mstackrealign.
- make iso took over a minute for any change, because staging r8169 rewrote
  the same files and every mtime moved; it now replaces files only if their
  contents differ (about 10 s).
- The loader's splash is redrawn in the boot manager's style, and the boot
  manager shows its cursor whenever a pointer device exists.
- xf86-input-libinput is out of the image again: with it Xorg took both
  keyboard and pointer from libinput and, on the user's laptop, neither worked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 17:43:44 +01:00

925 lines
32 KiB
C

// Drawing and the text console, on whatever display is bound. See include/gfx/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 "core/types.h"
#include "arch/x86.h"
#include "arch/memlayout.h"
#include "core/defs.h"
#include "gfx/theme.h"
#include "gfx/logo.h"
#include "dev/device.h"
#include "gfx/display.h"
#include "gfx/gfx.h"
#include "dev/vt.h"
#include "gfx/text.h"
#include "arch/schboot.h"
#include "gfx/ttf.h"
#include "arch/vmap.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.
// Whether the font in `cfont` is the built-in bitmap standing in, or a real
// typeface that was chosen.
//
// The distinction was implicit in `cfont == 0` until the built-in became a
// provider like any other, and losing it cost a boot: draw_glyph_px() installs
// the fallback lazily, a verbose boot draws before gfx_font_init() runs, and
// gfx_font_init() then saw a font already installed and returned without
// loading anything. The console stayed 8x16 for the whole boot while reporting
// itself as JetBrains Mono.
static int cfont_is_builtin;
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;
// Where `fb` points once gfx_accelerate() has run: a copy of the screen in
// RAM, which every drawing routine reads and writes, and which gfx_flush()
// copies onto the scanout -- `hw` -- a rectangle at a time.
//
// The framebuffer itself is device memory, uncached. Drawing into it directly
// cost nothing in QEMU, where it is RAM, and on a real graphics card it was
// the slowest thing the kernel did: a scroll reads every byte on the screen
// back across the bus, one uncached read per byte, and an anti-aliased glyph
// reads every pixel it blends. A screenful of boot log took long enough that
// photographs of the monitor caught the scroll half done, and a second CPU
// waiting for the console lock declared a deadlock. The copy is read instead,
// and the card is only ever written -- through a write-combining mapping, so
// a row goes out as bursts rather than one transaction per pixel.
static volatile uchar *hw;
static uchar *shadow;
static uint64 shadow_bytes;
static uint64 wc_phys; // the scanout ioremap_wc() mapped, and where
static volatile uchar *wc_ptr;
// What gfx_flush() still owes the scanout, in pixels, inclusive. Separate from
// the row span below: that one is for a second screen and is taken by
// somebody else.
static int fl_x0 = -1, fl_y0, fl_x1, fl_y1;
static void
dirty(int x0, int y0, int x1, int y1)
{
if (fb != (volatile uchar *)shadow)
return;
if (fl_x0 < 0) {
fl_x0 = x0; fl_y0 = y0; fl_x1 = x1; fl_y1 = y1;
return;
}
if (x0 < fl_x0) fl_x0 = x0;
if (y0 < fl_y0) fl_y0 = y0;
if (x1 > fl_x1) fl_x1 = x1;
if (y1 > fl_y1) fl_y1 = y1;
}
// 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;
hw = info->ptr;
// The framebuffer gfx_accelerate() mapped write-combined, coming back --
// vga_fb_restore() hands over its own uncached pointer to the same frames.
if (wc_ptr && info->phys == wc_phys)
hw = scanout.ptr = wc_ptr;
fb = hw;
if (shadow && (uint64)scanout.stride * scanout.height <= shadow_bytes) {
fb = (volatile uchar *)shadow;
// A new screen has none of what the copy holds.
dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1);
}
// 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);
dirty(x, y, x, 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++;
}
}
// The screen, `by` bytes towards the top. memmove() goes a byte at a time,
// which against RAM is merely slow; eight at a time is what a scroll of a
// screenful wants. Forward is safe because the destination is below the
// source.
static void
move_up(uint64 by, uint64 n)
{
volatile uchar *d = fb;
volatile uchar *s = fb + by;
if (((by | n | (uint64)fb) & 7) == 0) {
for (uint64 i = 0; i < n; i += 8)
*(volatile uint64 *)(d + i) = *(volatile uint64 *)(s + i);
return;
}
for (uint64 i = 0; i < n; i++)
d[i] = s[i];
}
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;
move_up(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);
cfont_is_builtin = 1;
}
// 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);
// Dirty before the early return: a blank cell is a background fill and
// nothing else, and that fill is what rubs the text cursor out. Marked
// only after the glyph, the blank was drawn to the back buffer and never
// copied to the screen -- a block left at the end of every line.
dirty(x, y, x + cell_w - 1, y + cell_h - 1);
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);
dirty(x, y, x + cell_w - 1, y + cell_h - 1);
}
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;
// A built-in font standing in is not a font having been chosen. Returning
// here on that basis is what kept the bitmap on screen for an entire
// verbose boot.
if (cfont && !cfont_is_builtin)
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;
}
}
// The size follows the screen: about 45 rows whatever the resolution, and
// never under 16 pixels per em, the bitmap's height. A fixed 16 was right
// on 1024x768 and unreadable on a 1440p monitor -- and asking for a smaller
// mode to get bigger text is no answer, because firmware on real machines
// often "sets" a smaller mode by leaving the panel at its native size and
// misreporting the row length, which is the sheared, repeated screen.
int em = scanout.height / 45;
if (em < 16) em = 16;
if (em > 40) em = 40;
if (ttf_open(&consttf, data, len, em) != 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, em) != 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;
cfont_is_builtin = 0;
}
// **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 && !cfont_is_builtin;
}
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;
move_up(line_size, total_size - line_size);
damage(0, (int)scanout.height - 1); // a scroll moves every row
dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1);
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;
gfx_flush();
}
// --- the copy in RAM ---------------------------------------------------------
// Copy what has been drawn since the last flush onto the scanout.
//
// Called where drawing ends rather than where it happens: after a message, a
// write(2) to a terminal, a terminal switch, the crash screen. A line of
// output is then one rectangle however many glyphs it took.
//
// Not a lock. Two CPUs flushing at once would only copy the same pixels
// twice, and a flush that finds another in progress leaves its damage for
// the next one -- so it can be called from the panic path and from inside an
// interrupt without either of them waiting on anything.
static uint64 flushing;
void
gfx_flush(void)
{
int x0, y0, x1, y1, bpp;
if (!hw || fb != (volatile uchar *)shadow || fl_x0 < 0)
return;
if (xchg(&flushing, 1) != 0)
return;
x0 = fl_x0; y0 = fl_y0; x1 = fl_x1; y1 = fl_y1;
fl_x0 = -1;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (int)scanout.width) x1 = (int)scanout.width - 1;
if (y1 >= (int)scanout.height) y1 = (int)scanout.height - 1;
bpp = (int)scanout.bpp / 8;
for (int y = y0; y <= y1 && x0 <= x1; y++) {
uint64 off = (uint64)y * scanout.stride + (uint64)x0 * bpp;
uint64 end = (uint64)y * scanout.stride + (uint64)(x1 + 1) * bpp;
// Bytes up to an eight-byte boundary, words, and bytes after. The
// source and the destination share an offset, so one alignment serves
// both.
while (off < end && (off & 7))
{ hw[off] = shadow[off]; off++; }
for (; off + 8 <= end; off += 8)
*(volatile uint64 *)(hw + off) = *(uint64 *)(shadow + off);
for (; off < end; off++)
hw[off] = shadow[off];
}
// Write-combined stores sit in the CPU's buffers until something drains
// them. They would get there; this says when.
asm volatile("sfence" ::: "memory");
flushing = 0;
}
// The crash screen, whatever else was happening. The CPU that was flushing
// may have been stopped halfway through by the panic, holding the flag for
// ever, and a crash report that never reaches the screen is the one outcome
// worse than a slow one.
void
gfx_flush_all(void)
{
flushing = 0;
dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1);
gfx_flush();
}
// Move drawing into RAM, and map the scanout write-combined.
//
// Once, from kmain, as soon as there is a page allocator. Everything drawn
// before that went straight to the framebuffer, and is drawn again here from
// the terminal's grid rather than read back from it -- reading the screen is
// the slow thing this exists to stop doing.
void
gfx_accelerate(void)
{
uint64 bytes;
void *wc;
if (!fb || shadow || scanout.phys == 0 || scanout.bpp < 8)
return;
bytes = (uint64)scanout.stride * scanout.height;
if ((wc = ioremap_wc(scanout.phys, bytes)) != 0) {
wc_phys = scanout.phys;
wc_ptr = (volatile uchar *)wc;
hw = scanout.ptr = wc_ptr;
fb = hw;
}
if ((shadow = vmap_alloc(bytes)) == 0) {
kinfo("gfx: no memory for a copy of the screen; drawing to it directly\n");
return;
}
shadow_bytes = bytes;
fb = (volatile uchar *)shadow;
gfx_clear(gfx_bg);
vt_repaint(vt_current());
gfx_flush();
kinfo("gfx: %d KB copy of the screen in RAM, %s\n", (int)(bytes / 1024),
wc ? "write-combined to the card" : "copied out uncached");
}
// The scanout itself, for /dev/fb0: a program drawing on the screen draws on
// the screen, not on the console's copy of it.
volatile uchar *
gfx_hw_ptr(void)
{
return hw;
}
/* ------------------------------------------------------------------ */
/* 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);
dirty(x0, y0, x0 + w - 1, 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);
}