#24 Textured font cache, badly handled

open-accepted
Sam Hocevar
None
5
2010-05-31
2010-05-25
No

I am wondering why textured fonts take so much time to load. Whenever it is loaded though, it is instantaneous.

I have noticed the followings using textured fonts:
If I display a string containing only a few characters ( let's say "123" ), it takes as long as 3 seconds to call the "Render" method ( with Batang font size 50 ).
If I use the same font size with the same facename later to Render "456" or any string with any character not rendered yet, it is instantaneous.

If I do the same using polygon fonts, it is always instantaneous.

I would like to load only the "glyphs" I need and don't worry about the unused ones until I need them.
I can see in the ftgl lib that it is lazy evaluated ( calling MakeGlyph in the Render method ) and it is a good thing, but there must be something under the hood ( probably in freetype2 ) that loads ALL the glyphs I don't need. Because 3 seconds seems a lot too much just to load "123". Especially if displaying "456" right after is instantaneous.

I would like to know if you have a solution for me.

Thank you,
Jean-Simon

Discussion

  • Ok after further investigation, I realized that big font files creates large textures ( 8192 x 8192 ) which seems to be the max texture size for my graphic card. I will try to use smaller textures maxima and post the results of my discoveries .

     
  • All right it seems that 1024x1024 textures work great with nvidia nvs295, 256 MB Video RAM on windows 7. I suggest that you use this value as well ( clamped to the OpenGL Max texture size ):

    maxTextureWidth = std::min( 1024, glMaxTextureSize );

    Note that the Batang font I'm using contains 30 000 glyphs and the Texture font tries to create a texture that contains all those characters even if it don't render them yet in the texture ( lazy evaluated ). The texture needed to render all glyphs at size 50 is 8192 * 27 648. Clamped to 8192x8192.

    Whenever a glyph cannot fit in the texture, it allocates another texture. If a single glyph does not fit in the texture, the lib fails to render the glyph. At this size, I suggest you switch to Polygonal Fonts . They are less beautiful but at that size, it does not really matter since you are most propably blind or 50 feet away from your computer ( The latter is my case ).

     
  • Sam Hocevar
    Sam Hocevar
    2010-05-31

    Thanks for investigating the issue. The implementation should definitely not render 30,000 glyphs upon initialisation. I will have a look at this.

     
  • Sam Hocevar
    Sam Hocevar
    2010-05-31

    • assigned_to: nobody --> sammy
    • status: open --> open-accepted
     
  • Well after the 1024x1024 optimization, I searched for the next bottle neck and I realized that allocating textures was not my bottle neck anymore. The bottle neck is in freetype2 when accessing the HDD ( fseek and fread 90000 times for my 30000 glyph font ). Now for my application I preload the file into an unsigned char* buffer then use the other ctor of the TextureFont which takes a memory buffer instead of a file path. It works great for me.

    I keep this buffer in memory until I need to load another font from another path which almost never happen and it is from a user action.

    In my application I need to try several sizes of the same font to see which one fits into a defined region.

    As for the "The implementation should definitely not render 30,000 glyphs upon initialisation", glyphs are not rendered upon initialisation. They are rendered only when you need them but they are parsed from the HDD and the sizes are computed. Which is not a problem for me since I don't parse from the HDD anymore and I actually need the sizes of the glpyhs.

     
  • Sean Morrison
    Sean Morrison
    2010-06-01

    Jean-Simon, nice investigative work on the texture font load times. It sounds like you're stressing FTGL and Freetype to a limit with that detailed font.

    As I mentioned in my sf.net message reply and as you figured out, the time it takes to load textured fonts is predominantly due to freetype2. FTGL does have a couple optimizations added to pre-compute the kerning on ascii characters for a given font so you don't get sporadic performance when advancing (it's a relatively expensive freetype call). Those are only on 128 glyphs for a given font, though.

    Most application developers make their font manager call Advance() on all characters you're going to use so that Freetype will pre-render them to a texture, done as part of a loading phase. Advance() doesn't draw anything but it does cause the glyph to get evaluated by freetype so that all subsequent drawing should be fast and consistent.

     
  • gkv_x64
    gkv_x64
    2010-06-07

    Hi! I have another problem using texture fonts on FTGL.
    Current implementation perform renderinf using GL_QUAD's with glBegin()/glEnd() for each glyph.

    One problem is that fixed pipeline is deprecated in modern OpenGL (I use OpenGL2 currently - thus this is not the limitation for me).

    But another problem is not cached rendering. FTGL provides rendering of arbitrary text each call (in layout or in font). However in my application text remains static most time and computation of vertices use extra time. Using uncached rendering (here I mean calling ::Render every frame) I detect very different behaviour on different configurations. In fact oldest GeForce FX5500 + Pentium4 CPU use very small amount of CPU time. But on Radeon HD3870 + Phenom2 550 CPU utilization become horrible! There are no a lot of text on the screen and 60% of CPU is absolutely insane for this task... More text - more CPU. Most problems under AMD/ATi GPUs, however NVIDIA drivers can do the same thing in some cases.

    With current implementation only way to cache text rendering - create display lists. However they are obsolete now. I prefer to create VBOs instead but can't find the way without changes in FTGL API. Some FTGLText object would be great but I have no idea how to do that better now...

    Also the problem for caching is ::FaceSize() method which clean up texture each time (even if setted size is equal to current!). In fact for performance resons this is preferrable to disallow creating TextureFont with not specifed and changable face size (or to add BIG comment for that...). If user tried to cache rendering with display lists or VBOs setting face size may broke the rendering...

    One little problem is using static field activeTextureID to bind texture lesser times. This looks VERY unsafe for multithreaded applications and/or multiple non-shared GL-contexts. And I don't think that ::ResetActiveTexture() could fully avoid real multithreading problems...

    Finally I tried to modify current texture fonts to get glyph vertices instead of direct rendering (I create VBOs for 'Text'-objects myself). However this looks like a hack but not a patch... If author helps to choose right way to implement this or do this himself - write me please :). My current patch is here (contains full modified files):
    http://sview.ru/files/ftgl-2.1.3~rc5_patch.7z