perf(font): cache per-codepoint advances in font units (#57)
Font::GetLineWidth called stbtt_GetCodepointHMetrics for every glyph on every invocation. InputField_HitTestCursor rescans the whole field once per character, making caret hit-testing O(n²) in HMetrics lookups. Memoise advances per Font via Font::AdvanceUnits: ASCII in a flat array, the rest in a map. Advances are cached in unscaled font *units* and rescaled per call — Glyph::advance in the FontAtlas is baked at kBaseSize and is wrong at any other size, so it cannot be reused. The per-glyph (int) truncation in GetLineWidth is preserved exactly. Adds the FontAdvanceCache CPU-only regression test covering size independence of the cached unit advance, the per-glyph-truncated rescale pipeline, cache-hit determinism, width scaling with size, and the non-ASCII map path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c1bd5fe244
commit
c169986835
5 changed files with 203 additions and 3 deletions
|
|
@ -63,5 +63,17 @@ namespace Crafter {
|
|||
float LineHeight(float size);
|
||||
float AscentPx(float size);
|
||||
float ScaleForSize(float size);
|
||||
|
||||
// Horizontal advance for `cp` in unscaled font units, cached per Font.
|
||||
// Stored in font units (not pixels) so it can be rescaled for any
|
||||
// `size`; Glyph::advance in the FontAtlas is baked at kBaseSize and is
|
||||
// wrong at other sizes, so it cannot be reused here. ASCII lands in a
|
||||
// flat array (the common path for caret hit-testing, which rescans the
|
||||
// whole field per character), everything else in the map.
|
||||
std::int32_t AdvanceUnits(std::uint32_t cp);
|
||||
|
||||
private:
|
||||
std::array<std::int32_t, 128> asciiAdvance_; // -1 = uncached
|
||||
std::unordered_map<std::uint32_t, std::int32_t> advanceUnits_;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue