refactor(render): the atlas is a declared fixed-cell grid, and staging checks it

The text pipeline stages a glyph's rect *origin* per instance and carries the
rect's *size* once per draw, so a backend whose glyphs have differing rect sizes
-- a TrueType packing trimmed to ink, which is Task 8's shape -- would have drawn
every glyph at whichever size it happened to be told, with nothing downstream
able to tell. Make that constraint structural rather than documented:

- TextBackend::cell_uv() declares the one cell size, and the contract (an atlas
  is a fixed-cell grid; a proportional face draws its glyphs *into* a uniform
  cell instead of trimming each rect to its ink, and why the pipeline needs it)
  is stated on the atlas accessors.
- glyph_is_cell() is the check; add_text()/set_cell() declare the cell from the
  backend, and the staging path asserts it for every glyph in a debug build.
  A release build draws on with the cell it was told, as before.
- Renderer::flush_text() takes u_glyph_uv from the backend, not from the batch,
  so the draw's one rect size can never come from whichever glyph was staged
  first.
This commit is contained in:
Martino Ferrari committed 2026-09-28 10:09:50 +02:00
1 parent e074f57ddf
commit ffc060d6ca
11 files changed
+364 -43

No files matched your search

+10
View File
@@ -40,6 +40,13 @@ class BitmapFont final : public TextBackend {
static constexpr u32 kAtlasRows = 7;
static constexpr u32 kAtlasWidth = kAtlasCols * kGlyphPx; ///< 128 texels
static constexpr u32 kAtlasHeight = kAtlasRows * kGlyphPx; ///< 56 texels
/// The atlas cell, normalised: one `kGlyphPx` square of the grid. Every glyph's
/// rect is this, and `cell_uv()` reports it -- one number, derived from the same
/// two constants `pack_atlas()` packs, so the grid the pipeline draws and the
/// grid the ink was written into cannot disagree.
static constexpr Vec2 kCellUV{static_cast<f32>(kGlyphPx) / static_cast<f32>(kAtlasWidth),
static_cast<f32>(kGlyphPx) / static_cast<f32>(kAtlasHeight)};
static_assert(kAtlasCols * kAtlasRows >= kGlyphCount, "atlas too small for the glyph set");
BitmapFont() noexcept = default;
@@ -71,6 +78,9 @@ class BitmapFont final : public TextBackend {
/// what a label outside the coverage set should show.
const Glyph* glyph(u32 codepoint) const override;
/// `kCellUV`: the 8x8 cell every glyph occupies.
Vec2 cell_uv() const override { return kCellUV; }
/// The packed atlas, as `TextBackend` sees it: the bytes `atlas_image()`
/// returns, `kAtlasWidth` by `kAtlasHeight`, GL_R8-ready. Never null.
const u8* atlas_pixels() const override;