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:
catbot 2026-06-16 17:00:48 +00:00
commit c169986835
5 changed files with 203 additions and 3 deletions

View file

@ -56,6 +56,23 @@ Font::Font(const std::filesystem::path& fontFilePath) {
this->ascent = ascent;
this->descent = descent;
this->lineGap = lineGap;
asciiAdvance_.fill(-1);
}
std::int32_t Font::AdvanceUnits(std::uint32_t cp) {
if (cp < asciiAdvance_.size()) {
if (asciiAdvance_[cp] < 0) {
int advance = 0, lsb = 0;
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
asciiAdvance_[cp] = advance;
}
return asciiAdvance_[cp];
}
if (auto it = advanceUnits_.find(cp); it != advanceUnits_.end()) return it->second;
int advance = 0, lsb = 0;
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
return advanceUnits_.emplace(cp, advance).first->second;
}
std::uint32_t Font::GetLineWidth(const std::string_view text, float size) {
@ -65,9 +82,7 @@ std::uint32_t Font::GetLineWidth(const std::string_view text, float size) {
while (i < text.size()) {
std::uint32_t cp = DecodeUtf8(text, i);
if (cp == 0) break;
int advance, lsb;
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
lineWidth += (int)(advance * scale);
lineWidth += (int)(AdvanceUnits(cp) * scale);
}
return lineWidth;
}