SchOS/kernel/dev/gfx.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

914 lines
31 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);
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;
}
}
// 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;
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);
}