PangoOpenGLRenderer - a tale of code duplication



[re-sent because the original mail is held for moderation for over a week,
a courtesy cc of any replies would be great, thanks!]

Hi!

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".

Preliminary thoughts

   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
   solution available.

   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
   opengl implementation.

   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
   needed.

   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
   code duplication.

[ 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.

Final thoughts

   I hope that my adventures with pango + opengl were somewhat
   entertaining, and maybe helpful to others who try to implement
   something similar.

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:
URL<http://data.plan9.de/pango.png>.

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).

Greetings,

-- 
                The choice of a
      -----==-     _GNU_
      ----==-- _       generation     Marc Lehmann
      ---==---(_)__  __ ____  __      pcg goof com
      --==---/ / _ \/ // /\ \/ /      http://schmorp.de/
      -=====/_/_//_/\_,_/ /_/\_\      XX11-RIPE




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