[Fonts]Notes on Pango Xft backend

Hi Keith,

I'm not particularly happy with the way the Xft backend for Pango
works at the moment ... it's complex, memory hungry, and relatively
slow. (Pango shaping with Xft is about twice as slow as Pango shaping
with the old X backend.)

So, I thought I'd write up a dump of:

 a) What operations Pango needs to do
 b) How it is implementing the operations currently
 c) How that might be improved on the Pango side
 d) What might be useful on the fontconfig side

If you have time to glance this over and give your thoughts, I'd
appreciate it. I'm not at all attached of the particular ways things
are implemented now or my ideas for possible improvements; what I'm
most interested in is having a reasonably efficient way of achieving
the end result.



The fundamental font operation in the Pango XFT backend (or any Pango
backend) is to map from a unicode codepoint and a PangoFontDescription
(family, style, weight, stretch, variant) to a particular PangoXftFont.

The Pango interfaces break this down into two pieces:

 A) PangoFontDescription,PangoContext => PangoFontSet
 B) PangoFontSet,codepoint => PangoXftFont

Operation A) is performed once for each segment of text with a different
PangoShaper or attributes; these segments of text range in length
from a few characters to an entire paragraph.

Operation B) is performed for every character in the source text.

The current Xft) implementation of operation A is somewhat complex -

a) The PangoFontDescription is turned into a XftPattern, and
   FcConfigSubstitute() and XftDefaultSubsitute() are called on 
   this pattern to integrate in the effects of the config file, and of
   display-specific settings.

b) The resulting pattern is split apart into multiple patterns -- one for
   each family name in the XftPattern; (that is, a pattern that has N
   family names is copied N times, with the family names replaced with
   each individual family name). 

c) Each of these "subpatterns" is matched against the font database, and
   if the match succeeds a PangoXftFont s created for the pattern and 
   added to the list in the PangoFontSet. (Success is defined by the result 
   having  the same family name as the original subpattern), 
Operation B) is simpler -

For each PangoXftFont in the PangoXftFontSet in turn:

 A) If the coverage information for that font hasn't previously been
    retrieved, get it.
 B) See, if the font has the given codepoint, if so, return it.

Operation A) is far too slow to implement into its entirety for each
small segment of text, so the bulk of the operation is cached -
a "infinite sized" map from PangoFontDescription to a list of patterns
is kept. (So, basically, everything but actually creating the PangoXftFont
objects and adding them to the fontset is cached.))

A second cache is kept of PangoXftFont; when creating a PangoXftFont
for a pattern, instead of always creating it from scratch, we instead 
check to see if there is already an outstanding font for the pattern,
and, if so, return it. We also keep a small number of fonts around
after the last refcount is dropped (MAX_FREED_FONTS, currently 16.)

Issues with the current implementation:

 * The PangoFontDescription => [list of patterns] map really can
   grow without bond, because each size is stored independently
   (and Pango allows very fine-grained sizes.) So, an app that
   allowed arbitrary zooming could easily end with a huge amount
   of cached lists of patterns.

   If we could cache patterns independent of size, then keeping
   around all patterns would be reasonable; however, fonts.conf
   can do arbitrary editing of patterns with conditionals based
   on size.

 * XftPattern is a bulky and inconvienient structure to work
   with; the end result of PangoFontDescription => [list of patterns]
   is stored as a pattern, but what we really are interested
   in are only the fields that actually effect display on 
   the screen (and coverage/metrics) ... that is, the fields
   that Xft internally stores per font:


   A place where this clearly shows up is in the 
   Pattern => PangoXftFont cache; we need to implement hash and 
   equality functions on large PangoXftPattern objects; the equality
   operation ends up taking 15-20% of the total execution time
   for layout-intensive operaitons.

 * The Pattern => PangoXftFont duplicates work that Xft does internally;
   When opening a pattern, Xft already checks to see if there is 
   another XftFont open for the same pattern.

   (So, first Pango does an hash lookup to make sure that 
   the no PangoXftFont is already open; if none is, then Xft will
   do a linear lookup to the same effect.)

Possible PangoXft improvements:

 * We could conceivably limit the size of the 
   PangoFontDescription => [list of patterns] map; this could result in
   a major loss of performance if the set of active font descriptions
   exceeded the size of the cache; but it prevents unbounded memory

 * The PangoFontDescription => [list of patterns] map could be changed into
   a PangoFontDescription => [list of XftFont] map. This is only really
   feasible in conjunction with limiting the size of the map, since
   keeping the XftFont objects around would increase the effects
   of a bloated cache.

 * We could drop the pattern => PangoXftFont cache and assume that 
   creating a PangoXftFont for a given patterns is:

   a) Sufficiently fast
   b) Cheap in memory (because XftFont is already doing duplicate 
      reduction in XftFontOpenPattern.

   Looking at the code of XftFontOpenPattern(), a) is unlikely to be 
   the case, because a large number of fields have to be individually
   extracted from the pattern, and there is a linear lookup for
   (Even if it isn't all the efficient, if we keep the number of
   these operations small, it doesn't matter.)

A combination of these ideas is:

 * Drop the two current caches, instead:

   Keep a size-limited cache:

    PangoFontDescription => PangoXftFontSet

   When creating the PangoXftFontSet, do everything longhand, create
   a separate PangoXftFont for XftFont.
   [ One problem with dropping the pattern => PangoXftFont cache, is 
     that you will end up creating many PangoXftFont objects for the
     same font at the same size, even though Xft will only create
     one XftFont object. ]

Possible Xft/fontconfig improvements:   

 * fontconfig could conceivably export a function like FcConfigSubstitute
   that produced not just the final match but also a range of sizes
   that it applied to by simply changing the size in the result.

   This would allow limiting the size of the 
   PangoFontDescription => [list of patterns] map.

 * Xft could export the idea of the "core" of a pattern ... an opaque
   structure holding:


   With operations to:
    a) hash
    b) compare
    c) create a XftFont from the "pattern core"

   This would allow both the PangoFontDescription => [List of patterns] cache
   and the Pattern => PangoXftFont cache to be implemented much more efficiently.
   Can't you just use XftFont for this? Why not just store a cache
   of PangoFontDescription => [List of XftFont]

   a) XftFont is fairly large. (It stores the complete pattern, for one thing)
   b) When you open an XftFont, it immediately opens the FT_Face
      for the font. We'd really like to avoid having to ever open the FT_Face
      for fonts that are in our patterns but not used.

 * A simpler variant on the preceding would be for Xft to 
   export a "trim a pattern" operation that trimmed a pattern down
   to the core fields.

 * Xft could export the necessary user-data mechanisms so that we
   could do reverse lookups from XftFont => PangoXftFont; this would
   allow is to avoid creating duplicate PangoXftFont for a single
   XftFont without keeping our own cache of PangoXftFont.

 * If the solution we take results in a large number of open XftFonts, it
   might be necessary to replace the linear lookup in XftFontOpenPattern
   with something smarter.
Fonts mailing list
Fonts XFree86 Org

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]