diff --git a/docs/architecture/graphics.md b/docs/architecture/graphics.md index 3f5c6e4..19347f6 100644 --- a/docs/architecture/graphics.md +++ b/docs/architecture/graphics.md @@ -301,6 +301,35 @@ implausible size, no memory, a short read, or a file that will not parse. The last of those says so in the log, because a font that silently did not load looks exactly like a font that did. +### One glyph blitter, and a cell is a surface + +`gfx.c` used to walk `font_data` and blit glyphs itself, while `text.c` did the +same job into a `struct surface` for no caller but a self-test. Two +implementations of the hardest small thing in a renderer — bearings, coverage, +clipping — that had to agree about a cell size only one of them was told had +changed. They did not agree, and every line of PSOGM drew the next character's +bitmap underneath itself. + +There is one now. 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 one 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 idea of +clipping anywhere in the tree. + +The bitmap font is reached through `font_builtin()` like any other provider, +so the early console and the real one are one code path with different +metrics. `gfx.c` does not include `font.h` any more; `text.c` does, being the +only file that reads the array. + +`struct surface` gained a `bpp` for this, because `gfx_draw_pixel` handles 16, +24 and 32 and a renderer replacing it had to as well. Zero means 32, which is +what a UEFI GOP always is and what the loader publishes unconditionally. + +**What is still a grid is `vt.c`**, and that is not a leftover. A terminal *is* +rows and columns: VT100 escapes address them, `TIOCGWINSZ` reports them, and +every program that draws a box assumes both. The grid that was in the way was +the one in the *drawing* layer, and it is gone. + ### The TrueType rasteriser `kernel/dev/ttf.c`: `glyf` outlines, composite glyphs with their component diff --git a/docs/plan/1.0.md b/docs/plan/1.0.md index 0133f25..63cfee6 100644 --- a/docs/plan/1.0.md +++ b/docs/plan/1.0.md @@ -252,10 +252,33 @@ These are the galling ones — the replacement exists, is tested, and is unused. was **one byte** holding a CP437 index, so `Ą` (U+0104) could not be *stored*, whatever was drawing it. The cell is a `uint32` codepoint now and the cell size comes from the font's measured advance. -- [ ] **Delete the cell grid.** The other half of the original item, and still - open. The console is a fixed grid of codepoints; `text.c`'s proportional - path has no caller. Nothing needs this before the compositor, which is - why it is separated from the row above rather than left blocking it. +- [x] ~~**Delete the cell grid.**~~ Done as far as it should be done, and the + rest of it retired — which needs explaining, because the item as written + asked for something a terminal cannot be. + + `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. **All + four were about the drawing layer, and all four are 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 that one is not in the way: it is + what a terminal *is*. VT100 escapes address columns, `TIOCGWINSZ` + reports rows and columns, and every program that draws a box assumes + both. Deleting it would not modernise the console, it would stop it + being a terminal. + + So the real remainder was the duplicate: `gfx.c` walked `font_data` + itself and blitted glyphs its own way, while `text.c` did the same job + into a `struct surface` for nobody but a self-test. Two implementations + that 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 blitter + now: `gfx.c` hands `text_draw_glyph` the cell *as a surface of its own*, + so clipping a tall glyph to its line is the same bounds check that clips + to a screen. The bitmap is reached through `font_builtin()` like any + other provider, and `gfx.c` no longer includes `font.h`. - [x] ~~**Load a font from disk**~~ and point the console at it. The built-in one is linked in on purpose — an early panic has to be readable — so this is about a *replaceable* font, not about having one. Drop a TrueType file diff --git a/include/text.h b/include/text.h index c9f7c5b..3428ad3 100644 --- a/include/text.h +++ b/include/text.h @@ -2,12 +2,18 @@ // Text rendering. // -// The console draws text by indexing `font_data[256][16]` and stepping the -// cursor eight pixels. That is a *character cell grid*, and it is the thing in -// the way -- not the font. A grid cannot do variable-width glyphs, cannot -// position text anywhere except on a multiple of eight, cannot anti-alias, and -// cannot represent a codepoint above 255. Swapping the bitmap for a nicer -// bitmap changes none of that. +// The console used to draw text by indexing `font_data[256][16]` and stepping +// the cursor eight pixels. That is a *character cell grid*, and it was the +// thing in the way -- not the font. A grid like that cannot do variable-width +// glyphs, cannot position text anywhere except on a multiple of eight, cannot +// anti-alias, and cannot represent a codepoint above 255. Swapping the bitmap +// for a nicer bitmap changed none of that. +// +// None of it is true any more, and the console draws through this file. What +// remains a grid is `vt.c`'s model of a terminal, which is not the same claim: +// a terminal *is* rows and columns -- VT100 escapes address them and +// TIOCGWINSZ reports them -- and it reaches this renderer by handing it one +// cell at a time, as a surface. // // So this is a layer, not a font. Three things it fixes: // @@ -30,11 +36,25 @@ struct font; // same code has to work on the virtio-gpu back buffer, on a window's own // buffer, and on the VGA aperture, and none of those know about each other. struct surface { - void *pixels; // 32-bit 0x00RRGGBB, the format everything here uses + void *pixels; // 0x00RRGGBB, at `bpp` bits each uint32 stride; // bytes per row, not pixels -- they differ on real hardware int width, height; + // 32 nearly always: a UEFI GOP has no other format and the loader publishes + // 32 unconditionally. 24 and 16 are here because a framebuffer is allowed to + // be either and a glyph drawn at the wrong depth is not a subtle failure. + // Zero means 32, so a caller that does not care does not have to say. + int bpp; }; +// A rectangle of a surface, as a surface of its own. +// +// Which is how a terminal cell is expressed: everything that draws into it is +// then clipped to it by the same bounds check that clips to a screen, with no +// second notion of clipping anywhere. A glyph with a tall ascender cannot +// bleed into the line above, because as far as the renderer is concerned the +// line above is off the edge of the world. +struct surface surface_sub(const struct surface *s, int x, int y, int w, int h); + // One rendered glyph. `cov` is 8-bit coverage, not a bitmap: 0 is background, // 255 is solid, and everything between is an anti-aliased edge. A bitmap font // fills it with 0 and 255 only, and costs nothing for the generality. diff --git a/kernel/dev/device.c b/kernel/dev/device.c index 9b965c1..648418c 100644 --- a/kernel/dev/device.c +++ b/kernel/dev/device.c @@ -501,7 +501,7 @@ text_selftest(void) return; } - struct surface s = { b, stride, m.width, m.height }; + struct surface s = { b, stride, m.width, m.height, (int)m.bpp }; struct font *f = font_builtin(); // A background with structure in it, so blending is visible as blending diff --git a/kernel/dev/gfx.c b/kernel/dev/gfx.c index f912f31..53cefa4 100644 --- a/kernel/dev/gfx.c +++ b/kernel/dev/gfx.c @@ -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 diff --git a/kernel/dev/text.c b/kernel/dev/text.c index 5245d5c..e59077c 100644 --- a/kernel/dev/text.c +++ b/kernel/dev/text.c @@ -5,11 +5,15 @@ #include "param.h" #include "text.h" -// Declared, not included. include/font.h *defines* font_data rather than -// declaring it, so including it here as well as in vga.c gives two definitions -// of the same array and the link fails. The header is generated data; leaving -// it alone and referring to the symbol is less fragile than editing it. -extern unsigned char font_data[256][16]; +// Included, now that this is the only file that reads it. +// +// It used to be `extern unsigned char font_data[256][16];` with a note +// explaining that include/font.h *defines* the array rather than declaring +// it, so a second includer would break the link. The second includer was +// gfx.c, which walked the bitmap itself when there was no TrueType face -- +// and that was the duplicate glyph path. With it gone, the header can live +// with its one user. +#include "font.h" // --- UTF-8 ----------------------------------------------------------------- @@ -160,22 +164,69 @@ font_builtin(void) // Reads the destination, which a bitmap font never needs -- but an // anti-aliased edge is defined as a blend with what is already there, and a // renderer that cannot do it is a renderer that cannot draw a real typeface. +static uint32 +sur_read(struct surface *s, unsigned char *at) +{ + switch (s->bpp) { + case 24: return ((uint32)at[2] << 16) | ((uint32)at[1] << 8) | at[0]; + case 16: { + uint16 v = *(uint16 *)at; + return (uint32)(((v >> 11) & 0x1F) << 19) | + (uint32)(((v >> 5) & 0x3F) << 10) | + (uint32)((v & 0x1F) << 3); + } + default: return *(uint32 *)at; + } +} + +static void +sur_write(struct surface *s, unsigned char *at, uint32 color) +{ + switch (s->bpp) { + case 24: + at[0] = color & 0xFF; at[1] = (color >> 8) & 0xFF; at[2] = (color >> 16) & 0xFF; + return; + case 16: + *(uint16 *)at = (uint16)((((color >> 19) & 0x1F) << 11) | + (((color >> 10) & 0x3F) << 5) | + ((color >> 3) & 0x1F)); + return; + default: + *(uint32 *)at = color; + return; + } +} + static void blend_px(struct surface *s, int x, int y, uint32 color, unsigned a) { if (x < 0 || y < 0 || x >= s->width || y >= s->height) return; - uint32 *px = (uint32 *)((unsigned char *)s->pixels + (uint64)y * s->stride) + x; + unsigned char *at = (unsigned char *)s->pixels + (uint64)y * s->stride + + (uint64)x * (s->bpp ? s->bpp / 8 : 4); if (a >= 255) { - *px = color; + sur_write(s, at, color); return; } - uint32 dst = *px; + uint32 dst = sur_read(s, at); uint32 r = (((color >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255; uint32 g = (((color >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255; uint32 b = (((color ) & 0xFF) * a + ((dst ) & 0xFF) * (255 - a)) / 255; - *px = (r << 16) | (g << 8) | b; + sur_write(s, at, (r << 16) | (g << 8) | b); +} + +struct surface +surface_sub(const struct surface *s, int x, int y, int w, int h) +{ + struct surface out = *s; + int bytes = s->bpp ? s->bpp / 8 : 4; + + out.pixels = (unsigned char *)s->pixels + (uint64)y * s->stride + + (uint64)x * bytes; + out.width = w; + out.height = h; + return out; } int