//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Issue #55: FontAtlas::Packer decides where a glyph goes in the atlas and, // once the atlas is full, which glyphs give up their cells. It knows no // pixels, so this drives it with made-up boxes and frames, without a device: // - until something is evicted it fills shelves as the old allocator did: // the first shelf tall enough with room at its end, else a new shelf // - while every glyph was used within framesInFlight + 1 frames, a full // packer gives nothing away; after that it does, the least recently used // first, and touching a glyph keeps it; without `evict` it never does // - a glyph wider than a cell takes the coldest adjacent cells, never one // whose glyph is in flight // - a glyph taller than every shelf spans several, taking free columns // across them before it evicts anything, and the free rows below the // last shelf too, and blocks just its columns on each; glyphs in flight // on every shelf block just theirs // - Touch marks every handle that still holds its cell, even after one // that doesn't, so the glyphs of a run shaped again stay // - a handle whose cell went to another glyph touches nothing // - every cell handed out and every region cleared is in a dirty span // - over a long random workload, placed the way FontAtlas places glyphs // (free room within a frame, evictions when the next one begins): cells // stay inside the atlas and apart, only cold glyphs are evicted, the // cleared regions cover the evicted cells and no live one, and a failure // or a placement without `evict` evicts nothing // // Pure CPU: no Vulkan device or WebGPU context at runtime. #include import Crafter.Graphics; import std; using namespace Crafter; namespace { int failures = 0; void Check(bool ok, std::string_view what) { std::println("{} {}", ok ? "PASS" : "FAIL", what); if (!ok) ++failures; } using Packer = FontAtlas::Packer; using Cell = Packer::Cell; constexpr std::uint32_t kNone = GlyphHandle::kNone; constexpr std::uint32_t kDepth = 3; // frames in flight, as a Window has them constexpr std::uint64_t kHold = kDepth + 1; // frames a glyph keeps its cell after its use std::uint32_t Place(Packer& p, int w, int h, std::uint64_t frame) { return p.Allocate(w, h, frame, kDepth, /*evict*/ true); } bool SameCell(const Cell& a, const Cell& b) { return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h; } bool Overlap(const Cell& a, const Cell& b) { return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h; } bool Contains(const Cell& outer, const Cell& inner) { return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.w <= outer.x + outer.w && inner.y + inner.h <= outer.y + outer.h; } // Some shelf's pending span covers `c`. Reads the spans without uploading. bool Dirty(Packer& p, const Cell& c) { bool covered = false; p.ForEachDirty([&](FontAtlas::DirtyRect& r) { covered = covered || (r.active && c.x >= r.minX && c.y >= r.minY && c.x + c.w <= r.maxX && c.y + c.h <= r.maxY); }); return covered; } void Upload(Packer& p) { p.ForEachDirty([](FontAtlas::DirtyRect& r) { r.Reset(); }); } // `count` cells of w×h, all used in frame `frame`. std::vector Fill(Packer& p, int count, int w, int h, std::uint64_t frame) { std::vector slots; for (int i = 0; i < count; ++i) slots.push_back(Place(p, w, h, frame)); return slots; } bool AllPlaced(std::span slots) { return std::ranges::none_of(slots, [](std::uint32_t s) { return s == kNone; }); } struct WorkloadResult { std::size_t placed = 0, evicted = 0, failed = 0, settled = 0; bool inside = true, apart = true, onlyCold = true, covers = true, spares = true; bool cleanFailures = true, freeEvictsNothing = true, dirty = true, handles = true; }; // Glyphs of random sizes, a few to a dozen a frame, in epochs of short ones // and of tall ones, so the short ones fill the rows with low shelves that the // tall ones need more than. Drawn each frame, as on a screen: what came in // the last twenty frames, and twenty glyphs from the first frames that never // stop being drawn (a UI's fixed labels), spread over the first shelves. A // new glyph gets free room in its frame, or waits: the next frame begins by // placing the waiting ones with evictions, as FontAtlas does. WorkloadResult Workload(int size, std::uint32_t seed) { Packer p(size); std::mt19937 rng(seed); struct Live { Cell cell; std::uint64_t born; std::uint64_t lastUse; GlyphHandle handle; bool fixed; }; std::unordered_map live; std::vector> waiting; std::size_t fixed = 0; WorkloadResult r; auto take = [&](std::uint32_t s, int w, int h, std::uint64_t frame, bool evict) { if (s == kNone) { r.cleanFailures = r.cleanFailures && p.Evicted().empty() && p.Cleared().empty(); return false; } r.freeEvictsNothing = r.freeEvictsNothing && (evict || p.Evicted().empty()); for (std::uint32_t v : p.Evicted()) { auto it = live.find(v); if (it == live.end()) { r.onlyCold = false; continue; } r.onlyCold = r.onlyCold && it->second.lastUse + kHold <= frame; r.covers = r.covers && std::ranges::any_of(p.Cleared(), [&](const Cell& c) { return Contains(c, it->second.cell); }); live.erase(it); ++r.evicted; } const Cell cell = p.CellOf(s); r.inside = r.inside && cell.w == w && cell.h == h && cell.x >= 0 && cell.y >= 0 && cell.x + w <= size && cell.y + h <= size; for (const auto& [slot, l] : live) r.apart = r.apart && !Overlap(cell, l.cell); for (const Cell& c : p.Cleared()) { for (const auto& [slot, l] : live) r.spares = r.spares && !Overlap(c, l.cell); r.dirty = r.dirty && Dirty(p, c); } r.dirty = r.dirty && Dirty(p, cell); const bool isFixed = frame < 30 && fixed < 20 && rng() % 2 == 0; fixed += isFixed; live[s] = { cell, frame, frame, p.Handle(s), isFixed }; ++r.placed; return true; }; for (std::uint64_t frame = 10; frame < 4010; ++frame) { for (auto [w, h] : std::exchange(waiting, {})) { if (take(p.Allocate(w, h, frame, kDepth, /*evict*/ true), w, h, frame, true)) ++r.settled; else ++r.failed; } for (auto& [slot, l] : live) { if (l.fixed || l.born + 20 > frame) { p.Touch(slot, frame); l.lastUse = frame; } } const int maxH = (frame / 500) % 2 == 0 ? 12 : 40; const int n = static_cast(rng() % 12); for (int k = 0; k < n; ++k) { const int w = 4 + static_cast(rng() % 37); const int h = 4 + static_cast(rng() % static_cast(maxH - 3)); if (!take(p.Allocate(w, h, frame, kDepth, /*evict*/ false), w, h, frame, false)) waiting.emplace_back(w, h); } if (frame % 50 == 0) { // Every live handle still holds its cell. Touching it in its own // last frame leaves the workload as it was. for (const auto& [slot, l] : live) { r.handles = r.handles && p.Touch(std::span{ &l.handle, 1 }, l.lastUse); } Upload(p); } } return r; } } // namespace int main() { // ── 1. Filling: the first shelf tall enough with room, else a new one. ── { Packer p(100); const std::uint32_t a = Place(p, 30, 10, 0); const std::uint32_t b = Place(p, 30, 10, 0); const std::uint32_t c = Place(p, 30, 10, 0); const std::uint32_t d = Place(p, 30, 10, 0); const std::uint32_t e = Place(p, 10, 8, 0); const std::uint32_t f = Place(p, 20, 12, 0); Check(SameCell(p.CellOf(a), {0, 0, 30, 10}) && SameCell(p.CellOf(b), {30, 0, 30, 10}) && SameCell(p.CellOf(c), {60, 0, 30, 10}), "a shelf fills left to right"); Check(SameCell(p.CellOf(d), {0, 10, 30, 10}), "a glyph without room on a shelf opens the next one"); Check(SameCell(p.CellOf(e), {90, 0, 10, 8}), "a shorter glyph takes the first shelf with room"); Check(SameCell(p.CellOf(f), {0, 20, 20, 12}), "a taller glyph opens a shelf of its own height"); Check(p.Evicted().empty() && p.Cleared().empty(), "filling evicts nothing"); Check(Dirty(p, p.CellOf(a)) && Dirty(p, p.CellOf(f)), "every cell handed out is in a dirty span"); Check(Place(p, 0, 10, 0) == kNone && Place(p, 10, 101, 0) == kNone, "an empty box, or one bigger than the atlas, gets no cell"); } // ── 2. Full: nothing to give while every glyph is in flight. ─────────── { Packer p(64); const auto cells = Fill(p, 16, 16, 16, 1); Check(AllPlaced(cells), "sixteen 16x16 cells fill a 64x64 atlas"); bool held = true; for (std::uint64_t frame = 1; frame < 1 + kHold; ++frame) { held = held && Place(p, 16, 16, frame) == kNone && p.Evicted().empty(); } Check(held, "for framesInFlight + 1 frames after their use, the glyphs keep their cells"); Check(p.Allocate(16, 16, 1 + kHold, kDepth, /*evict*/ false) == kNone && p.Evicted().empty(), "then, without evict, still none gives way"); const std::uint32_t s = Place(p, 16, 16, 1 + kHold); Check(s != kNone && p.Evicted().size() == 1, "with it, one gives way, and only one"); Check(p.Cleared().size() == 1 && SameCell(p.Cleared()[0], p.CellOf(s)) && Dirty(p, p.Cleared()[0]), "its texels are cleared, and the clearing is in a dirty span"); Check(Place(p, 8, 8, 1 + kHold) != kNone && p.Evicted().size() == 1, "a smaller glyph takes one cold cell too"); } // ── 3. The least recently used goes first; a touch keeps a glyph. ────── { Packer p(64); std::vector cells; for (std::uint64_t frame = 1; frame <= 16; ++frame) cells.push_back(Place(p, 16, 16, frame)); p.Touch(cells[0], 19); // frame 1's glyph, used again in frame 19 std::vector order; for (int i = 0; i < 15; ++i) { if (Place(p, 16, 16, 20) == kNone || p.Evicted().size() != 1) break; order.push_back(p.Evicted()[0]); } Check(order == std::vector(cells.begin() + 1, cells.end()), "full, the glyphs give way in the order they were last used"); Check(Place(p, 16, 16, 20) == kNone, "the glyph touched in a frame still in flight stays"); } // ── 4. A wide glyph takes adjacent cold cells, never one in flight. ──── { Packer p(64); const std::uint32_t a = Place(p, 16, 16, 1); const std::uint32_t b = Place(p, 16, 16, 9); const std::uint32_t c = Place(p, 16, 16, 2); const std::uint32_t d = Place(p, 16, 16, 3); Check(AllPlaced(Fill(p, 12, 16, 16, 9)), "three more shelves, in flight in frame 10"); const std::uint32_t s = Place(p, 32, 16, 10); Check(s != kNone && SameCell(p.CellOf(s), {32, 0, 32, 16}), "a 32-wide glyph takes the two cold cells beside each other"); const auto evicted = p.Evicted(); Check(evicted.size() == 2 && std::ranges::count(evicted, c) == 1 && std::ranges::count(evicted, d) == 1, "it evicts just those two"); Check(std::ranges::count(evicted, a) == 0 && std::ranges::count(evicted, b) == 0, "the colder glyph beside the one in flight stays, as does that one"); Check(p.Cleared().size() == 2 && Dirty(p, p.Cleared()[0]) && Dirty(p, p.Cleared()[1]), "both cells are cleared, in a dirty span"); } // ── 5. A glyph taller than every shelf spans several. ────────────────── { Packer p(64); // Eight shelves of eight 8x8 glyphs; shelf 1's are in flight. for (int shelf = 0; shelf < 8; ++shelf) Fill(p, 8, 8, 8, shelf == 1 ? 9 : 1); Upload(p); const std::uint32_t s = Place(p, 16, 24, 10); Check(s != kNone && SameCell(p.CellOf(s), {0, 16, 16, 24}), "a 24-tall glyph spans three 8-row shelves, past the one in flight"); Check(p.Evicted().size() == 6, "it evicts the six glyphs in its columns"); bool cleared = p.Cleared().size() == 6; for (const Cell& c : p.Cleared()) cleared = cleared && Contains(p.CellOf(s), c) && Dirty(p, c); Check(cleared, "their cells are cleared, in a dirty span"); const std::uint32_t t = Place(p, 40, 20, 10); Check(t != kNone && SameCell(p.CellOf(t), {16, 16, 40, 20}), "a glyph spanning shelves blocks its columns on each, and no more"); } { Packer p(64); // Eight 8-row shelves, each holding one 40x8 glyph, all in flight: // columns 40 to 63 are free on every shelf. Check(AllPlaced(Fill(p, 8, 40, 8, 1)), "eight 40-wide glyphs open eight shelves"); const std::uint32_t s = p.Allocate(16, 16, 1, kDepth, /*evict*/ false); Check(s != kNone && SameCell(p.CellOf(s), {40, 0, 16, 16}) && p.Evicted().empty(), "a 16-tall glyph takes free columns across two shelves, without evict too"); } { Packer p(64); // Eight shelves of eight 8x8 glyphs, and on shelf k the glyph in // column k is in flight: every shelf holds one, every pair of shelves // has them in two columns side by side. std::vector cells; for (int shelf = 0; shelf < 8; ++shelf) { for (std::uint32_t c : Fill(p, 8, 8, 8, 1)) cells.push_back(c); } for (std::uint32_t c : cells) { if (p.CellOf(c).x == p.CellOf(c).y) p.Touch(c, 9); } const std::uint32_t s = Place(p, 16, 16, 10); Check(s != kNone && SameCell(p.CellOf(s), {16, 0, 16, 16}) && p.Evicted().size() == 4, "glyphs in flight on every shelf block only their own columns"); } { Packer p(64); // Six full shelves of 8x8 glyphs in flight, a seventh holding three // in columns 0 to 23, and 8 free rows below it: a 12-tall glyph is // taller than the free rows and than any shelf. Check(AllPlaced(Fill(p, 51, 8, 8, 9)), "six and a bit 8-row shelves, in flight in frame 10"); const std::uint32_t s = p.Allocate(10, 12, 10, kDepth, /*evict*/ false); Check(s != kNone && SameCell(p.CellOf(s), {24, 48, 10, 12}) && p.Evicted().empty(), "a glyph taller than the free rows spans the last shelf and them"); const std::uint32_t t = Place(p, 64, 4, 10); Check(t != kNone && SameCell(p.CellOf(t), {0, 60, 64, 4}), "the free rows it left below take later glyphs"); } // ── 6. Touch marks every handle that still holds its cell. ───────────── { Packer p(32); const auto cells = Fill(p, 4, 16, 16, 1); std::vector run; for (std::uint32_t c : cells) run.push_back(p.Handle(c)); const std::uint32_t n = Place(p, 16, 16, 1 + kHold); // takes cells[0], the first found Check(n == cells[0] && p.Evicted().size() == 1, "one of four cold glyphs gives way"); Check(!p.Touch(run, 2 + kHold), "a run whose first glyph went touches false"); // Only the new glyph is cold kHold frames on: the run's other three // were marked although the first handle no longer held its cell. const std::uint32_t m = Place(p, 16, 16, 1 + 2 * kHold); Check(m != kNone && p.Evicted().size() == 1 && p.Evicted()[0] == n, "the run's other glyphs were marked used past the stale handle"); } // ── 7. A handle whose cell went to another glyph touches nothing. ────── { Packer p(32); const std::uint32_t a = Place(p, 32, 32, 1); const std::array handles{ p.Handle(a) }; Check(p.Touch(handles, 2), "a handle to a live cell touches its glyph"); const std::uint32_t b = Place(p, 32, 32, 2 + kHold); Check(b == a && p.Evicted().size() == 1 && p.Evicted()[0] == a, "the cell goes to another glyph"); Check(p.Handle(b).generation != handles[0].generation, "with a new generation"); Check(!p.Touch(handles, 3 + kHold), "the old handle no longer touches it"); Check(Place(p, 32, 32, 2 + 2 * kHold) != kNone, "and did not keep the new glyph from being evicted in its turn"); const std::array foreign{ GlyphHandle{ 999, 0 } }; Check(!p.Touch(foreign, 9), "a handle to no slot touches nothing"); } // ── 8. A long random workload. ───────────────────────────────────────── { const WorkloadResult r = Workload(256, 55); std::println("random workload: {} placed ({} when a frame began), {} evicted, {} failed", r.placed, r.settled, r.evicted, r.failed); Check(r.evicted > 1000 && r.settled > 1000, "the workload evicts, when frames begin"); Check(r.inside, "every cell is the size asked for and inside the atlas"); Check(r.apart, "no cell overlaps a live one"); Check(r.onlyCold, "only glyphs no frame in flight uses are evicted"); Check(r.covers, "the cleared regions cover every evicted cell"); Check(r.spares, "and no live one"); Check(r.dirty, "every cell handed out and every region cleared is in a dirty span"); Check(r.cleanFailures, "a failure evicts nothing"); Check(r.freeEvictsNothing, "a placement without evict evicts nothing"); Check(r.handles, "every live glyph's handle still holds its cell"); } if (failures == 0) std::println("\nAll font atlas packer checks passed."); else std::println("\n{} font atlas packer check(s) FAILED.", failures); return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; }