Skip to content

Commit 77e4bc9

Browse files
committed
web: reduce GlyphCache lock contention with FontSize handle
Add GlyphCache::FontSize, a lock-free handle that resolves a font size once and provides glyph/kern/textWidth/cellHeight lookups without re-acquiring the mutex. Update TileGenerator rendering loops (pin labels, heatmap text, debug graphics) to resolve the font once per context. Also check stbtt_InitFont return value and use unique_ptr for font_info_. Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
1 parent 8bcfb0f commit 77e4bc9

5 files changed

Lines changed: 151 additions & 104 deletions

File tree

src/web/src/font_atlas.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
namespace web {
1313

14+
inline GlyphCache::FontSize fontAtlasGetFont(int height_px)
15+
{
16+
return glyphCache().getFont(height_px);
17+
}
18+
1419
inline int fontAtlasTextWidth(std::string_view text, int height_px)
1520
{
1621
return glyphCache().textWidth(text, height_px);

src/web/src/glyph_cache.cpp

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#include <algorithm>
77
#include <cmath>
88
#include <cstddef>
9+
#include <memory>
910
#include <mutex>
11+
#include <stdexcept>
1012
#include <string_view>
1113
#include <vector>
1214

@@ -22,15 +24,14 @@ constexpr int kNumGlyphs = kLastChar - kFirstChar + 1;
2224
} // namespace
2325

2426
GlyphCache::GlyphCache(const unsigned char* ttf_data, unsigned int /*ttf_size*/)
27+
: font_info_(std::make_unique<stbtt_fontinfo>())
2528
{
26-
font_info_ = new stbtt_fontinfo;
27-
stbtt_InitFont(font_info_, ttf_data, 0);
29+
if (!stbtt_InitFont(font_info_.get(), ttf_data, 0)) {
30+
throw std::runtime_error("Failed to initialize font from TTF data");
31+
}
2832
}
2933

30-
GlyphCache::~GlyphCache()
31-
{
32-
delete font_info_;
33-
}
34+
GlyphCache::~GlyphCache() = default;
3435

3536
const GlyphCache::SizeSlot& GlyphCache::getSlot(int font_height) const
3637
{
@@ -41,10 +42,10 @@ const GlyphCache::SizeSlot& GlyphCache::getSlot(int font_height) const
4142
}
4243

4344
SizeSlot& slot = cache_[font_height];
44-
slot.scale = stbtt_ScaleForPixelHeight(font_info_, font_height);
45+
slot.scale = stbtt_ScaleForPixelHeight(font_info_.get(), font_height);
4546

4647
int ascent, descent, line_gap;
47-
stbtt_GetFontVMetrics(font_info_, &ascent, &descent, &line_gap);
48+
stbtt_GetFontVMetrics(font_info_.get(), &ascent, &descent, &line_gap);
4849
slot.ascent = static_cast<int>(std::round(ascent * slot.scale));
4950
const int desc_px = static_cast<int>(std::round(descent * slot.scale));
5051
slot.cell_height = slot.ascent - desc_px;
@@ -67,11 +68,11 @@ const GlyphCache::SizeSlot& GlyphCache::getSlot(int font_height) const
6768
auto& t = temps[i];
6869

6970
int advance_raw, lsb_raw;
70-
stbtt_GetCodepointHMetrics(font_info_, cp, &advance_raw, &lsb_raw);
71+
stbtt_GetCodepointHMetrics(font_info_.get(), cp, &advance_raw, &lsb_raw);
7172
t.advance = static_cast<int>(std::round(advance_raw * slot.scale));
7273

7374
unsigned char* bmp = stbtt_GetCodepointBitmap(
74-
font_info_, 0, slot.scale, cp, &t.gw, &t.gh, &t.xoff, &t.yoff);
75+
font_info_.get(), 0, slot.scale, cp, &t.gw, &t.gh, &t.xoff, &t.yoff);
7576
if (bmp && t.gw > 0 && t.gh > 0) {
7677
const size_t n = static_cast<size_t>(t.gw) * t.gh;
7778
t.bitmap.assign(bmp, bmp + n);
@@ -112,64 +113,95 @@ const GlyphCache::SizeSlot& GlyphCache::getSlot(int font_height) const
112113
return slot;
113114
}
114115

115-
int GlyphCache::textWidth(std::string_view text, int font_height) const
116+
// --- FontSize (lock-free handle) -------------------------------------------
117+
118+
GlyphCache::FontSize::FontSize(const SizeSlot& slot,
119+
const stbtt_fontinfo* font_info)
120+
: slot_(slot), font_info_(font_info)
121+
{
122+
}
123+
124+
GlyphCache::GlyphInfo GlyphCache::FontSize::glyph(char ch) const
125+
{
126+
const int idx = static_cast<unsigned char>(ch) - kFirstChar;
127+
if (idx < 0 || idx >= kNumGlyphs) {
128+
return {.alpha = nullptr,
129+
.bmp_width = 0,
130+
.bmp_height = 0,
131+
.x_offset = 0,
132+
.y_offset = 0,
133+
.advance = 0};
134+
}
135+
const auto& cg = slot_.glyphs[idx];
136+
const unsigned char* alpha_ptr
137+
= cg.bmp_width > 0 ? slot_.alpha.data() + cg.alpha_offset : nullptr;
138+
return {.alpha = alpha_ptr,
139+
.bmp_width = cg.bmp_width,
140+
.bmp_height = cg.bmp_height,
141+
.x_offset = cg.x_offset,
142+
.y_offset = cg.y_offset,
143+
.advance = cg.advance};
144+
}
145+
146+
int GlyphCache::FontSize::kern(char ch1, char ch2) const
147+
{
148+
const int raw = stbtt_GetCodepointKernAdvance(font_info_, ch1, ch2);
149+
return static_cast<int>(std::round(raw * slot_.scale));
150+
}
151+
152+
int GlyphCache::FontSize::textWidth(std::string_view text) const
116153
{
117154
if (text.empty()) {
118155
return 0;
119156
}
120-
const auto& slot = getSlot(font_height);
121157
int width = 0;
122158
for (size_t i = 0; i < text.size(); ++i) {
123159
const int idx = static_cast<unsigned char>(text[i]) - kFirstChar;
124160
if (idx >= 0 && idx < kNumGlyphs) {
125-
width += slot.glyphs[idx].advance;
161+
width += slot_.glyphs[idx].advance;
126162
}
127-
// Add kerning with next character.
128163
if (i + 1 < text.size()) {
129-
width += kern(font_height, text[i], text[i + 1]);
164+
width += kern(text[i], text[i + 1]);
130165
}
131166
}
132167
return width;
133168
}
134169

170+
int GlyphCache::FontSize::cellHeight() const
171+
{
172+
return slot_.cell_height;
173+
}
174+
175+
// --- GlyphCache public API (convenience, locks per call) -------------------
176+
177+
GlyphCache::FontSize GlyphCache::getFont(int font_height) const
178+
{
179+
return FontSize(getSlot(font_height), font_info_.get());
180+
}
181+
182+
int GlyphCache::textWidth(std::string_view text, int font_height) const
183+
{
184+
return getFont(font_height).textWidth(text);
185+
}
186+
135187
int GlyphCache::textHeight(int font_height) const
136188
{
137-
return getSlot(font_height).cell_height;
189+
return getFont(font_height).cellHeight();
138190
}
139191

140192
int GlyphCache::cellHeight(int font_height) const
141193
{
142-
return getSlot(font_height).cell_height;
194+
return getFont(font_height).cellHeight();
143195
}
144196

145197
GlyphCache::GlyphInfo GlyphCache::glyph(int font_height, char ch) const
146198
{
147-
const int idx = static_cast<unsigned char>(ch) - kFirstChar;
148-
const auto& slot = getSlot(font_height);
149-
if (idx < 0 || idx >= kNumGlyphs) {
150-
return {.alpha = nullptr,
151-
.bmp_width = 0,
152-
.bmp_height = 0,
153-
.x_offset = 0,
154-
.y_offset = 0,
155-
.advance = 0};
156-
}
157-
const auto& cg = slot.glyphs[idx];
158-
const unsigned char* alpha_ptr
159-
= cg.bmp_width > 0 ? slot.alpha.data() + cg.alpha_offset : nullptr;
160-
return {.alpha = alpha_ptr,
161-
.bmp_width = cg.bmp_width,
162-
.bmp_height = cg.bmp_height,
163-
.x_offset = cg.x_offset,
164-
.y_offset = cg.y_offset,
165-
.advance = cg.advance};
199+
return getFont(font_height).glyph(ch);
166200
}
167201

168202
int GlyphCache::kern(int font_height, char ch1, char ch2) const
169203
{
170-
const auto& slot = getSlot(font_height);
171-
const int raw = stbtt_GetCodepointKernAdvance(font_info_, ch1, ch2);
172-
return static_cast<int>(std::round(raw * slot.scale));
204+
return getFont(font_height).kern(ch1, ch2);
173205
}
174206

175207
const GlyphCache& glyphCache()

src/web/src/glyph_cache.h

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#pragma once
1010

1111
#include <map>
12+
#include <memory>
1213
#include <mutex>
1314
#include <string_view>
1415
#include <vector>
@@ -21,17 +22,8 @@ struct Color;
2122

2223
class GlyphCache
2324
{
24-
public:
25-
// Initialize from raw TTF data (must remain valid for the cache lifetime).
26-
GlyphCache(const unsigned char* ttf_data, unsigned int ttf_size);
27-
~GlyphCache();
28-
29-
// Total pixel width of a rendered string (includes kerning).
30-
int textWidth(std::string_view text, int font_height) const;
31-
// Line height in pixels.
32-
int textHeight(int font_height) const;
33-
3425
// Per-glyph rendering info.
26+
public:
3527
struct GlyphInfo
3628
{
3729
const unsigned char* alpha; // bitmap, bmp_width * bmp_height bytes
@@ -42,15 +34,6 @@ class GlyphCache
4234
int advance; // horizontal advance in pixels
4335
};
4436

45-
// Cell height (line height, same for all glyphs at a given size).
46-
int cellHeight(int font_height) const;
47-
48-
// Look up a glyph. Returns {nullptr,...} for space / unknown.
49-
GlyphInfo glyph(int font_height, char ch) const;
50-
51-
// Kerning adjustment between two characters (in pixels).
52-
int kern(int font_height, char ch1, char ch2) const;
53-
5437
private:
5538
struct CachedGlyph
5639
{
@@ -71,9 +54,42 @@ class GlyphCache
7154
std::vector<unsigned char> alpha; // packed glyph bitmaps
7255
};
7356

57+
public:
58+
// Lock-free handle to a resolved font size. Retrieve via getFont()
59+
// and use for batch glyph/text operations without per-call locking.
60+
class FontSize
61+
{
62+
public:
63+
GlyphInfo glyph(char ch) const;
64+
int kern(char ch1, char ch2) const;
65+
int textWidth(std::string_view text) const;
66+
int cellHeight() const;
67+
68+
private:
69+
friend class GlyphCache;
70+
FontSize(const SizeSlot& slot, const stbtt_fontinfo* font_info);
71+
const SizeSlot& slot_;
72+
const stbtt_fontinfo* font_info_;
73+
};
74+
75+
// Initialize from raw TTF data (must remain valid for the cache lifetime).
76+
GlyphCache(const unsigned char* ttf_data, unsigned int ttf_size);
77+
~GlyphCache();
78+
79+
// Acquire a FontSize handle (locks once, then all lookups are lock-free).
80+
FontSize getFont(int font_height) const;
81+
82+
// Convenience methods — each locks internally per call.
83+
int textWidth(std::string_view text, int font_height) const;
84+
int textHeight(int font_height) const;
85+
int cellHeight(int font_height) const;
86+
GlyphInfo glyph(int font_height, char ch) const;
87+
int kern(int font_height, char ch1, char ch2) const;
88+
89+
private:
7490
const SizeSlot& getSlot(int font_height) const;
7591

76-
stbtt_fontinfo* font_info_;
92+
std::unique_ptr<stbtt_fontinfo> font_info_;
7793
mutable std::mutex mutex_;
7894
mutable std::map<int, SizeSlot> cache_;
7995
};

0 commit comments

Comments
 (0)