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>
This commit is contained in:
Nexoniarz 2026-09-23 17:57:16 +02:00
commit c84d06c5b1
6 changed files with 183 additions and 72 deletions

View file

@ -16,7 +16,6 @@
#include "memlayout.h"
#include "defs.h"
#include "theme.h"
#include "font.h"
#include "logo.h"
#include "device.h"
#include "display.h"
@ -40,13 +39,16 @@
//
// So the console draws from a real typeface now, and a real typeface decides
// its own metrics. These start at the bitmap's size because the console has to
// work from the first panic onwards -- before gfx_font_init() has run there is
// no font, and 8x16 against font_data is exactly what there always was.
// The bitmap's own geometry, which is fixed forever: font_data is
// `uchar[256][16]` and each row is eight bits. Naming it separately from the
// cell size is not pedantry -- the two were the same number, then one of them
// started moving, and the code that kept indexing font_data by the moving one
// read straight off the end of every glyph.
// 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
@ -56,8 +58,9 @@ static int cell_h = BITMAP_H;
#define CHAR_WIDTH cell_w
#define CHAR_HEIGHT cell_h
// The typeface, once there is one. `cfont` is the switch: null means fall back
// to the bitmap, which is what early boot and any font failure get.
// 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;
@ -298,60 +301,45 @@ gfx_putc(int c, uint32 color)
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;
// No font yet: the bitmap, exactly as before. Anything it cannot hold is
// drawn as a space rather than as the wrong glyph -- `?` was at least honest
// about being a substitute, and a CP437 picture of something unrelated is
// not.
//
// BITMAP_W/H and not cell_w/h: the bitmap's geometry is a property of
// font_data, not of whatever the console decided a cell is. They agree
// whenever cfont is null, and the assert below is what keeps that true.
// 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) {
uchar g = cp < 256 ? (uchar)cp : ' ';
for (int r = 0; r < BITMAP_H; r++) {
uchar bits = font_data[g][r];
for (int c = 0; c < BITMAP_W; c++)
gfx_draw_pixel(x + c, y + r, (bits & (0x80 >> c)) ? fg : bg);
}
return;
cfont = font_builtin();
cfont_baseline = cfont->ops->baseline(cfont);
}
// 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.
// 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);
struct glyph gl;
if (cp == ' ' || cfont->ops->glyph(cfont, cp, &gl) != 0)
return; // no such glyph: the cell stays background
if (cp == ' ')
return; // nothing to draw over the 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;
// 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;
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));
}
}
cell = surface_sub(&screen, x, y, cell_w, cell_h);
text_draw_glyph(&cell, cfont, 0, cfont_baseline, cp, fg);
}
void