perf(ui): LRU-evict the shaped-run cache instead of clearing it (#123)
At kMaxShapedRuns (8192) UIRenderer::ShapeText did a full shapedRuns_.clear(). A stream of unique strings (FPS counters, timers) alongside many stable labels periodically nuked every stable entry, forcing a full-UI reshape the next frame. Track recency with an auxiliary std::list<const ShapedRunKey*> (front = most recently used) holding pointers to the keys owned by the node-based map. A hit splices its entry to the front; an overflow pops the least-recently-used entry from the back and erases just that one — both O(1). The hot set of labels, reshaped every frame, stays at the front and survives, so the churn of unique strings only recycles the cold tail. Output is byte-identical either way. InvalidateFont now drops matching entries from both the map and the LRU list. Adds ShapedRunCacheSize()/IsShapedRunCached() introspection hooks (the policy isn't observable through output or atlas.dirty) and a ShapeTextCache test that asserts the cache plateaus at a fixed cap (evict-one, not clear-all), the hot label survives a churn past the cap, and an untouched cold string is evicted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2783e47674
commit
1d7d9f7b8e
3 changed files with 115 additions and 6 deletions
|
|
@ -325,6 +325,17 @@ export namespace Crafter {
|
|||
// reallocated at the same address would otherwise alias stale runs.
|
||||
void InvalidateFont(const Font& font) noexcept;
|
||||
|
||||
// Shaped-run cache introspection (issue #123). Hits/misses are
|
||||
// byte-identical and a re-shape of an already-rasterised string leaves
|
||||
// the atlas clean, so the LRU eviction policy isn't observable through
|
||||
// output alone — these let tests assert that the cache stays bounded
|
||||
// and that the hot set survives an overflow. IsShapedRunCached is a
|
||||
// pure query: it does not bump recency.
|
||||
std::size_t ShapedRunCacheSize() const noexcept { return shapedRuns_.size(); }
|
||||
bool IsShapedRunCached(const Font& font, float pxSize,
|
||||
std::array<float,4> color,
|
||||
std::string_view utf8) const;
|
||||
|
||||
std::uint16_t FontAtlasImageSlot() const noexcept { return fontAtlasImageSlot_; }
|
||||
std::uint16_t FontAtlasSamplerSlot() const noexcept { return fontAtlasSamplerSlot_; }
|
||||
|
||||
|
|
@ -362,12 +373,25 @@ export namespace Crafter {
|
|||
struct ShapedRun {
|
||||
std::vector<GlyphItem> glyphs; // origin-relative
|
||||
float advance = 0;
|
||||
// Position of this run's key in shapedRunsLru_ (front = most
|
||||
// recently used). Lets a hit splice the entry to the front and an
|
||||
// overflow pop the least-recently-used entry, both O(1).
|
||||
std::list<const ShapedRunKey*>::iterator lruIt{};
|
||||
};
|
||||
// Soft cap. A pathological caller drawing a fresh string every frame
|
||||
// would grow this without bound; on overflow we drop everything and
|
||||
// rebuild (correctness is unaffected, only the hit rate after a flush).
|
||||
// would grow this without bound; on overflow we evict the single
|
||||
// least-recently-used run (issue #123) rather than clearing the whole
|
||||
// cache. The hot set of stable labels — reshaped every frame, so always
|
||||
// near the front — stays resident, while a churn of unique strings
|
||||
// (FPS counters, timers) only recycles the cold tail. Correctness is
|
||||
// unaffected either way; this just avoids the periodic full-UI reshape
|
||||
// spike the old clear() caused.
|
||||
static constexpr std::size_t kMaxShapedRuns = 8192;
|
||||
std::unordered_map<ShapedRunKey, ShapedRun, ShapedRunKeyHash> shapedRuns_;
|
||||
// LRU recency order for shapedRuns_; front = most recently used. Holds
|
||||
// pointers to the keys owned by the map — stable across rehash because
|
||||
// unordered_map is node-based — so eviction never re-hashes the world.
|
||||
std::list<const ShapedRunKey*> shapedRunsLru_;
|
||||
|
||||
ImageSlot outImageSlot_;
|
||||
ImageSlot fontAtlasImageSlot_;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue