Table of Contents
Threads and Lifetimes
Threads
| Object | May be shared between threads? |
|---|---|
face, font |
Yes. Several threads may lay out text with one font at once, and ask a face for fonts. |
layout, and its lines, runs and glyphs |
Yes, while it does not change: hit testing only reads it. layout-text's into lays it out again in place, so do that only where no other thread reads it. |
outline |
Yes. It never changes. |
rect |
Yes. One a layout holds changes as the layout is laid out again; one selection-rectangles returns never does. |
rasterizer |
Yes, from any number of threads at once. FreeType draws with a face from one thread at a time, so the glyphs of one face are drawn one at a time. |
glyph-atlas |
Yes. Release a scale while no thread asks for glyphs at it. |
field-atlas |
Yes. Fields for different glyphs are made in parallel. |
cached-outline and cached-quadratics may be called from any thread.
Arrays returned from caches, such as the coverage rasterize-glyph returns
and the vectors cached-quadratics returns, are shared with later
callers: read them, but do not modify them.
A common arrangement for a game: lay out text on whichever thread owns it, with faces made at startup; make distance fields or rasterized glyphs on job-system workers through a shared field or glyph atlas; and upload the atlases' changed pages on the render thread at a frame boundary.
Lifetimes
Nothing needs releasing by hand. Faces and fonts hold HarfBuzz's objects, and are freed once nothing holds them; everything renderers make from them is kept with them and goes with them.
| Object | What happens to it |
|---|---|
face |
Freed, with what renderers keep with it, once nothing holds it or any of its fonts. destroy-face frees it and its fonts at once; with-face does when its body is left. |
font |
Freed, with what renderers keep with it, once nothing holds it. destroy-font frees it at once, and its face too if make-font made it; with-font does when its body is left. |
rasterizer, glyph-atlas, field-atlas |
Plain Lisp objects: what they make is kept with the faces and fonts they draw. |
layout, outline, rect |
Plain Lisp data, collected like any other. |
Destroying a face or font twice does nothing. See Fonts.
What holds what
- A font holds its face, and a face the fonts it gave. Keep a face, and
face-fontgives the same font for a size each time. - A layout holds its fonts. No font is freed while text laid out with it is around, so a layout can always be drawn.
- A page holds its font or face. A glyph atlas's
font-atlasholds its font, whichatlas-fontreads, and theatlas-dataof a field atlas'sface-atlasis the face, so a renderer that keeps a page keeps what it was made from. - Layouts outlive destroyed fonts. After
destroy-faceordestroy-font, a layout keeps its glyphs and positions, and hit testing it still works, but it cannot be laid out or drawn again.
What renderers keep
Each face and font keeps what renderers make from it, under each
renderer's cache key, with face-cache and font-cache, and a font keeps
its own outlines:
- a font keeps its outlines, from
cached-outlineandcached-quadratics; - a face keeps a FreeType library and face of its own for each rasterizer of the raster backend, and a font the glyph images each draws;
- a font keeps its atlas at each scale of each glyph atlas,
font-atlas, until the last hold on the scale is released, and a face its atlas of each distance field atlas,face-atlas.
Nothing is evicted glyph by glyph while the face or font lives: a font
keeps every glyph it has drawn, and its atlases add pages as they fill. All of it goes when
the face or font does, so an application that draws text at many sizes over
a long time frees what it no longer uses by dropping the fonts of those
sizes. A glyph atlas's glyphs at a scale go once the last hold on the
scale is released, so text drawn at a zoom no view draws at any more keeps
nothing. What one renderer made goes too when its key is forgotten, with
forget-cache-key, as the renderer is released, and what a rasterizer, a
glyph atlas or a distance field atlas made once the collector takes it, so
a face or font that outlives them keeps nothing of them.
Allocation
Measured on SBCL, with DejaVu Sans and a 43-character, four-line layout:
| Call | Allocates |
|---|---|
layout-text |
Much: about 59 KB here, 13 KB for a short line such as "-123.456". Lay out when the text changes, not every frame. |
layout-text with into, of a simple line |
Nothing, once the layout has held a line as long: a label whose text changes every frame lays it out again into its layout. |
do-glyphs, layout, line, run and glyph accessors |
Nothing. |
caret-position, caret-segment |
Nothing. |
next-caret-index, previous-caret-index |
Nothing. |
hit-test, move-caret |
A few hundred bytes, on input events only. |
selection-rectangles |
Its result: a list of rectangles. |
layout-glyphs |
Its result: a fresh vector. |
cached-outline, cached-quadratics, rasterize-glyph, glyph-region (both atlases), face-font for a size asked before |
Nothing, once the glyph is cached. |
map-layout-glyphs (both), map-layout-fields |
Nothing, once the layout's glyphs are cached. |
So a layout drawn every frame allocates nothing in steady state, whichever
way it is drawn: walk it with do-glyphs, draw the caret with
caret-segment, and look its glyphs up with cached-quadratics, a
rasterizer, a glyph atlas or a field atlas. The test suite checks this.
Selection rectangles change only when the selection does: keep them until
then.