PangoOpenGLRenderer - a tale of code duplication
- From: gtk-i18n-list plan9 de
- To: gtk-i18n-list gnome org
- Subject: PangoOpenGLRenderer - a tale of code duplication
- Date: Sat, 15 Jul 2006 22:48:37 +0200
[re-sent because the original mail is held for moderation for over a week,
a courtesy cc of any replies would be great, thanks!]
I recently decided to write an opengl game. A major problem with opengl
is the lack of font/text suppport, and thus I had to decide wether I'd
go with one of the many more-or-less primitive opengl text hacks or
go for the right solution - leveraging pango.
Since I learned "interesting things" while implementing this, I thought
I'd share this with you, maybe it is of some help to others (or even to me
Just a single warning: "long".
Pango is slow, which was no news to me, but since the most practical
solution was to cache text strings fully in textures, I hoped rendering
speed was not a major problem, and indeed, it turned out that with
lots of caching, speed IS NOT a problem, at least compared to the high
quality of typesetting that I get with pango.
First tier of heaven (hell?) - pangoft2
Now, my first implementation was using pangoft2, rendering strings into
graymaps and feeding those into an alpha tetxure. This worked fine,
although quite important features were missing, which soon became
quite painful. Most prominently, there was no way to get colour
out of pangoft2 - the only options you had are all ink, all transparent,
and text ink on transparent background.
The problem with that is that you can't switch it off - inked text on
background is limited, but certainly readable. A "black" rectangle is
not, so I had to preparse markup to filter out colours.
This worked relatively fine, but, due to my inefficient use of the
results (every pangoft2 rendered string became a texture, a real waste
of memory especially on opengl targets not supporting NPOT textures), I
looked for a better solution.
Most pressing, though, was getting colour support.
Second tier of heaven (hell?) - pangocairo
Looking at the existing options, I naturally found the cairo backend.
Apart from being much slower (>>50%) than pangoft2, and having lower
visual quality than pangoft2 on unix (I suspect a problem with
generating premulitplied alpha within cairo, which is unfortunately the
only available output format), the biggest problem was that the results
on win32 differed significantly from those of pangoft2, due to the use of
native win32 fonts (I assume).
Worse, the resulting text was completely unreadable on win32 when
antialiasing was enabled, so I had to disable antialiasing on win32
compledtely to get at least readable text.
For some time, I ran with both cairo and ft2 renderers, selecting the
most appropriate backend (pangoft2 for quality and speed, cairo for
features=color) per string based on wether it used any colour or not.
Clearly, this was not the final solution.
Third tier of heaven (hell?) - pangoopengl
I was looking at implementing my own opengl backend for pango for some
time, even before I started to use pangocairo, but everytime I tried, I
miserably failed. What kept me from trying was that I wasn't too keen
on managing texture catalogs for glyphs (== the opengl glyph cache),
especially not in C :), a 100% non-pango related problem, so I didn't
try very hard.
Yesterday though I set out to solve it no matter what, and finally
succeeded, although the taste of victory is rather bitter, as I had
to throw some of my personal design standards overboard. A primitive
texture catalog infrastructure was finished after an hour hacking, so
what I feared most was not the problem I thought it would be.
But pango was a different beast! Clearly not designed to be reused by
third party software! Or so!
My first attempt was to subclass the PangoFT2Renderer. Unfortunately,
this quickly proved futile, as most of the required functionality
from PangoFT2Renderer, PangoFT2FontMap and PangoFT2Font is kept
private. One example is the glyph rendering and caching - all of that
is private, so subclassing PangoFT2Renderer is not possible (I learned
about PANGO_ENABLE_BACKEND early on, btw.)
The second attempt was writing my own PangoOpenGLRenderer not derived
from PangoFT2Renderer. Most of that work was copying pangoft2-render.c
over to my own directory and starting a big rename party. The files are
still pretty similar - it was, after all, just simple code duplication
- but due to the important parts of the API being private, the only
It soon turned out that this wasn't enough, though - although the
FT2 fontmap and font classes provide some abstracted interface for
the benefit of PangoFT2Renderer /glyph info caching for example),
again a lot of other interfaces are private, so I had to clone
pangoft2-fontmap.c and pangoft2.c, and then some. Those files are even
more similar to the original files, as the only reason for cloning them
was to get at their private parts (uhum :), as the classes already
provide whats required, just not the everybody.
After some more hacking (all this took about 8 hours - long for the
problem, at least by my standards), I was almost there.
Of course, I forgot about PangoFCFontmap. Yes, you guess correctly,
it also doesn't expose vital parts of it's API (or better FT2-PI),
in this case pango_fc_font_get_raw_extents.
While cloning, I naively assumed that identifiers starting with _ (a
despicable practise in itself, widely used within pango sources, given
that the C language reserves identifiers starting with an underscore at
file scope) are private, while those without are not - a mistake, as
pango_fc_font_get_raw_extents is indeed private to pango.
I don't know wether there is a public API equivalent of that method,
but I suspect not, as there would be no reason to use the private one.
Again, just an assumption.
At that point I was close to cloning pangofcfontmap, too - only the
massive amount of code duplicaiton that would result from that kept me
from doing it.
Instead, I threw another of my precious design standards overboard and
just declared pango_fc_font_get_raw_extents in my implementation and
called it - that still sends shivers through my body, I feel really
awful. Drive with your eyes closed, so to speak. Just worse.
But lets ignore those bleak seconds, and enjoy the result: I had
a working pango backend for opengl, with nice and fast glyph
caching, with all the features I needed, and it was actually quite
fast (ok, it slowed down the software rendering, as walking all
those pango structures using thousands of method calls that all
dynamically check pointer validity was quite slow - I immediately used
G_DISABLE_CAST_CHECKS in my copy, without giving it a second thought),
as glyph compositing by cpu dominated the rendering time with a hardware
I saw heavenly light by then - till I switched into fullscreen and lost
my opengl context, and therefore my glyph cache.
Well, of course, I thought "I just have to clear the glyph cache
info and be done with - EZEEEE". I already did that with my texture
catalog, and browsing through pango documentation and code I quickly
found pango_fc_font_map_cache_clear, which, well, obviously did what I
Or maybe not.
Turned out it didn't - it seems to do something with fontsets,
something I didn't really want to understand at that point (remember I
just wanted to implement a renderer).
Hmm, ok, lets walk the fonts and...
No, not either: that part of the implementation is hidden, too.
Again, I was close to just cloning pangofcfontmap, but... No.
The alternatives I saw would be some elaborate system to remember
every font used (which is actually the job of pangofcfontmap, or so
I figured), or associating a generation counter incremented on every
context change with every glyph cache entry, which was a true waste of
memory, but a stable and working way to solve the problem without more
[ The ugly, and far from production quality (or at least,
library quality), is now part of the CFClient repository
(http://software.schmorp.de) - please don't look for a finished product
there, I just mention it because I think it would be inappropriate to talk
a lot about code nobody can find :). ]
I do feel quite uneasy - the generation counter is simply ugly,
declaring and using an internal function is outright evil, and copying
files and renaming things doesn't exactly feel like the right thing(tm)
either, but it works so far, and a better solution seems not to be
possible with pango.
Gone are the days of pangocairo and pangoft2 - oh well, not the latter,
as pangofc is part of pangoft2, so I still have to link and ship it,
but at leats it's relatively small.
Morale of the story
Well, maybe I am doing something very wrong, but it seems that pango
was not designed for code reuse at all. Which is puzzling, because it
uses all those OO techniques, such as multiple classes in a hierarchy
(pangofc => pangoft2), all of which seemingly just in support of each
other, as the interesting bits are not open and just provided for the
benefit of themselves. I think a much simpler design that obviously
made it impossible to subclass, combining pangofc into pangoft2 and not
exposing APIs that only the other class can make use of anyway, would
be smaller, faster, more maintainable.
Or well, maybe I am missing something very crucial.
I hope that my adventures with pango + opengl were somewhat
entertaining, and maybe helpful to others who try to implement
Thanks a lot for providing pango - a lot of people would (actually did)
declare me a lost case for trying to combine pango with a realtime
opengl application - but that part turned out very nicely, and with
some optimisations pango is quite up to the task (well, at least on my
machine, which is not the slowest one, but who cares).
A temporary screenshot of pango in action can be found here:
It still used pangoft2 and pangocairo at that stage, but the current
version very much looks the same. And no, the font is unnaturally large,
and no, I have no idea wether the arabic script makes any sense (I would
be glad for corrections).
The choice of a
----==-- _ generation Marc Lehmann
---==---(_)__ __ ____ __ pcg goof com
--==---/ / _ \/ // /\ \/ / http://schmorp.de/
-=====/_/_//_/\_,_/ /_/\_\ XX11-RIPE
] [Thread Prev