Introduce the concept of a shared glyph cache#398
Conversation
JimBobSquarePants
left a comment
There was a problem hiding this comment.
This looks fantastic. I wish I had thought of it!
I think we can drop the configuration and use truthy default behaviour. The current differences are imperceptible to the human eye.
For the updated reference image just reuse your new output. I use pinga with lossless optimization to optimize them for storage. https://css-ig.net/pinga
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces an opt-in per-canvas shared glyph-outline cache for DrawText calls, allowing glyph outlines built once to be reused across multiple text draws on the same canvas.
Changes:
- Adds a new
GlyphCacheDefaultsExtensionsAPI to enable/disable the shared glyph cache viaConfiguration. - Threads an optional shared
Dictionary<CacheKey, List<GlyphRenderData>>fromDrawingCanvas<TPixel>intoRichTextGlyphRenderer, with ownership tracking so injected caches aren't cleared on dispose. - Adds a benchmark comparing the per-call cache against the per-canvas shared cache.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/ImageSharp.Drawing/Processing/GlyphCacheDefaultsExtensions.cs | New public extensions to set/get a shared-glyph-cache flag on Configuration. |
| src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs | Accepts an optional shared cache; conditionally clears it on dispose; promotes CacheKey/GlyphRenderData visibility to internal. |
| src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs | Lazily creates and passes a per-canvas shared cache to each renderer instance when enabled. |
| tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextSharedGlyphCache.cs | New benchmark exercising shared vs. per-call glyph cache across many runs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| internal static bool GetSharedGlyphCache(this Configuration configuration) | ||
| => configuration.Properties.TryGetValue(typeof(SharedGlyphCacheKey), out object? value) && value is true; |
| this.DisposePendingImageResources(); | ||
| } | ||
|
|
||
| // Release the per-canvas shared glyph-outline cache. |
| /// screen location on a cache hit. | ||
| /// </summary> | ||
| private struct GlyphRenderData | ||
| internal struct GlyphRenderData |
| /// <para> | ||
| /// Unlike <see cref="DrawTextRepeatedGlyphs"/> (a single <c>DrawText</c> call, where the | ||
| /// per-call cache already captures repeats), this draws <see cref="RunCount"/> separate | ||
| /// <c>DrawText</c> calls that reuse a common alphabet across runs - the cross-call reuse the |
|
Thanks @JimBobSquarePants -- I'll work on updating. Just to be clear, |
Prerequisites
Description
tl;dr: Adds an opt-in per-canvas glyph-outline cache (Configuration.SetSharedGlyphCache(true)) that reuses glyph outlines across DrawText calls on a canvas, making text-heavy rendering up to ~3× faster and cutting allocations ~2.6× (404 MB → 155 MB at 1000 text runs).
Draft pull request for a possible shared glyph cache. Shows promising performance benchmarks in Starling, especially when rendering an entire screen of text.
Hoping to see if you had appetite for merging in something like this -- it would be great to have. Or if there's anything I'm missing please LMK!
Notes:
Early benchmarks here, the full suite is still running:


EDIT - benchmarks from full suite finished: