JTalton's picture

Fonts - Yet again

Windows XP
I converted my application over from using TAO.SDL and the SDL TTF library for rendering fonts to using the OpenTK fonts.
The OpenTK fonts don't have the correct pixel precision and the look blurry. Also the bottom of the font is getting cut off.
Am I missing a setting? I did find that if I change the font size to 8.25 instead of 9.0 the bottom of the font does not get cut off.

SDL Version

OpenTK Version (Updated with fixes resulting from this thread)

Program.cs3.13 KB


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
BlueMonkMN's picture

I got the code, and it looks like many of the features I put in locally are missing, so it's going to be hard for me to test in my usual project. Word wrap and alignment should be very easy with MeasureCharacterRanges because it handles it automatically. Is there a reason that wasn't included? Also, it looks like the measurement of the overall size is not supported in the same way (I was proposing the size be returned from the Prepare call)... what's the proper way for the client code to measure the text? Make a separate call to MeasureText? Or am I missing something?

JTalton's picture

If the size has already been calculated at the Prepare call time, it does seem like that may be a good place to return it.

JTalton's picture

With the fix that went in for the Italics measurements, I am not seeing the crash anymore. :)

the Fiddler's picture

Ah, life is complex... but it's always good when bugs disappear on their own :)

A little patience, all requested features will appear in due time. I'm working on the glyph cache right now, testing different algorithms/approaches. I'm getting close to a solution that is simpler than the current one, with slightly less-efficient packing but with the additional capability for garbage collection (unlike the current one).

I'm also simpligying the API. If you think about it, two functions are all that's needed: MeasureText (text layout information, e.g. bounding box and glyph rectangles for caret placement) and DrawText. These will work with Font objects directly (no need to create TextureFonts manually); text layout will be controlled by optional parameters; caching will (probably) be handled internally.

Imagine System.Windows.Forms.TextRenderer, just OpenGL accelerated.

All this is about 50% ready.

BlueMonkMN's picture

So will there be multiple overloads for DrawText, one that accepts pre-calculated results of MeasureText and one that just accepts a string and a rectangle? Or will we be limited to either having DrawText calculating the layout every time text is drawn or always having to calculate ("Measure") a layout as a separate step before drawing text?
I would guess there's an inherent difference between Forms.TextRenderer for OpenGL text rendering in the fact that OpenGL allows caching of vertex information as an optimization, so I'd be interested to understand what the plan is in this respect.

BTW, I'm kinda enjoying the extra leisure time waiting for OpenTK fonts to work themselves out -- I can play video games without feeling obligated to work on SGDK2 until fonts are ready ;)

haymo's picture

I too lost this disscussion... Just finished implementing a whole text-rendering machine - based on the TextureFont design. Its solves a lot of issues having been important for me. (Multiple fonts, arbitrary texture contents, multiple texture sheets managed in a single texture manager, performant render queue caching and size handling etc. Will report some details if SO's interested. The design somehow went away from the original OpenTK design though.)

Now I wonder, what the status for MeasureCharacterRanges (which works fine for me on Windows) on Linux is? Anyone got this working? It gives complete nonsens on all characters (mono 1.2.6) ? In the OpenTK code I see you using GDI+ to MeasureText - but how to make this work on Linux? Did I miss that part?

thanks for any suggestions!

the Fiddler's picture

On Linux, it just works (tm). You have to specify the correct dllmap for gdiplus.dll (libgdiplus.so), define a couple of pinvokes and that's it. The most fragile part is the use of reflection to access the native handles of StringFormat and Font objects, but even that is working fine on both .Net (2.0-3.5) and Mono.

Things would have been much easier of the BCL MeasureCharacterRanges implementation didn't create garbage. Unfortunately it does, which is why I opted to reimplement this function using native GDI+ methods.

Can you please share some details on your architecture (and especially on the sheets?) I'll try to describe the code I'm currently working on (not yet in SVN):

  • Text is layed out in VBOs using MeasureCharacterRanges. This code can handle word wrapping, RTL and vertical text directions.
  • The API interfaces with System.Drawing.Font objects directly (no need for TextureFonts).
  • Glyphs are rasterized on demand and cached in texture sheets (there is a sheet manager to handle multiple shees).
  • VBOs are sorted by texture sheet to minimize state changes.
  • VBOs are not cached (see below).

Caching rasterized glyphs in textures provides an enormous speed benefit. Caching VBOs does help, but not as much. The problem I am facing now is that:
1. If the sheets are write-only, the VBOs can be cached easily. It is also easy to achieve good packing using a btree. However, write only texture sheets can be inefficient memory-wise (i.e. you can't evict glyphs which are only used once or twice).

2. If the sheets are read-write, you can use an LRU strategy to evict unused glyphs. On the other hand, glyphs cannot be packed as tightly (line based approach instead of btrees. I've also played with a quadtree, but results were not good). The other problem is that all VBOs are invalidated as soon as you evict any glyph.

Right now, I've opted for approach (1), mainly because it is simpler. Apart from some corner cases it is unlikely that a lot texture space will be lost in unused glyphs, but more testing is needed before reaching a final verdict.

How did you approach this problem? Are glyph sheets mutable or immutable? Do you cache text-layout results?

haymo's picture

@GDI+: has the garbage produced by System.Drawing.Graphics.MeasureCharacterRanges been the only reason to switch to PInvokes? If yes - this would be no issue for me, since it is only called once for each render item (ie. a string). The items texture is than cached together with the size. Unfortunately, MeasureCharacterRanges gives wrong results on Linux? Or maybe its only because some strange StringFormat options I'm using (TextRenderingHint.AntiAliasGridFit, Nowrap | NoClip)? Will keep on testing this.

@TextureManagers: TextureSheets (the way I implemented it) are semi-immutable. they fill up (using the binary tree) successively with new texture items. New sheets are created on demand. Since I need to have multiple Fonts (Name,Size) the cached items are later accessed by keys collected in RenderQueues. (This is one advantage compared to the original OpenTK design - but may also be a bottleneck from a performance point of view I guess. However, it may easily be improved by pooling the render queue items if needed). The RenderQueue also stores the overall size for all items after rendering. Im not using VBOs at all - but if one needs to, I would not store them into the RenderQueues either (see below).

The texture manager can clear all (/possibly any or some) sheets, if the request to create a new one has failed or if some other memory issues arise. In case a texture cache sheet is cleared, all classes holding thoses RenderQueues are signaled by a simple eventing mechanism. They have to re-cache their (really needed) items than. This may be a nuissance in case of a public API since users must not forget to register for the TextureCacheCleared event. But it's more than fine for me - using the rendering only internally. Alternatively, the RenderQueues could be invalidated remotely by the TextureManager.

If it comes to rendering, the texture coordinates are queried from the TextureManager, the texture sheet is changed if needed and the item is rendered straight away. Since the TextureManager might move the textures around in a more sophisticated implementation (mutable sheets), the coordinates are not given away on caching but stored in the sheet. This and the state changes needed if rendering items from different sheets (cannot render all between one single Begin()...End()) made me not using VBO's at all. The performance does not suffer too much in my case. Therefore I am at your approach 1 - even without the need of immutable sheets. Dont know, if I will ever need this ...

Not sure, what you mean by "text-layout results"? The TextureManager receives and stores items as Bitmaps. The content of a Bitmap is now drawn characterwise. But it might just as well be the result of a complete word or any arbitrary image. This is up to the interpreter class transforming the textual input into RenderQueues via bitmaps and not at the texture manager site.

the Fiddler's picture

Ah, I see. Our approaches are both similar and different at the same time.

The main difference is that you render the whole string in a Bitmap and cache this Bitmap in a texture sheet. This has the advantage of simplicity and speed. However, how does this work with "dynamic text", i.e. text that changes per frame (fps counters, timers etc)? Maybe I was doing something wrong, but when I tried this approach back in March, updating even a 256x256 region would kill performance (~10 fps).

What OpenTK does, is store individual characters in the texture sheet ("a", "b", etc) and then assemble these into VBOs for rendering. This has the advantage of lower memory usage (a glyph is stored once but used many times) and better "dynamic text" performance (building a VBO is much faster than uploading rasterized text to a texture). On the other hand, the implementation is a little more complicated, since you now have two steps (upload glyphs, build VBO) and two possible caches (glyphs/VBOs).

Avoiding garbage is the only reason to use pinvokes for MeasureCharacterRanges. There are times when OpenTK needs to call this function per frame (i.e. uncached text), so generating 1 GC object per character is just not an option.

With pinvokes you call gdipMeasureCharacterRanges directly, avoiding the issue. The code is actually simple - the only problem is that you need access to a couple of internal properties. I'm a little worried about this, but since it works on both Mono and .Net, there probably isn't a problem. It also looks that this part of the BCL hasn't been touched since v2.0, which is good - I'll be keeping an eye out for future framework updates, though.

Alright, it seems we are following the same approach here: individual sheet elements are immutable, but whole sheets can be cleared (with an event signaling this situation).

In the new text renderer, I've opted to perform all caching internally. This makes life a lot simpler for the user (otherwise he'd have to watch out and respond to the TextureCacheCleared event, which is a possible failure point).

What I haven't decided yet, is whether the user should be able to flag that a specific string should be cached. This will depend on the relative performance of uploading a VBO vs using a cached one: if this is close, it might be possible to avoid caching VBOs at all. If there is a significant difference, some form of cache will have to be used - either a simple LRU queue or an API to allow the user to say, "I won't change this string, cache it."

As I mentioned above, the texture sheet stores individual glyphs that is (character + Font) combinations. A btree is used for tight packing and the coordinates are stored internally in a Dictionary for fast lookup.

[Text layout]
Now, since the sheets hold individual glyphs, we've got to build a VBO in order to render actual text.

When the TextPrinter draws a string, it starts by uploading all glyphs to texture sheets (of course, existing glyphs are not uploaded again - the Dictionary is used to make this process fast). After this:
1. it uses MeasureCharacterRanges to find out the character extents for the string.
2. looks up glyph coordinates from the texture sheets,
3. builds one or more VBOs using the above information (one VBO per texture involved),
3b. optionally caches the VBOs
4. renders the VBOs.

If a VBO exists in the cache, TextPrinter skips to step 4 above. That's it more or less.

Of course you avoid all this by caching complete text runs to the texture sheets :) How do you plan to tackle uncached text? Or will you disallow this completely?

haymo's picture

Ups, I have caused some confusion ;) I do cache single characters within each string! The difference is the way they are identified within the cache. Since the cache only stores bitmaps (and does not know about the content) with a size - it is up to the interpreter layer to split each word into chars or not. For most cases (simple general text interpreter) single chars will be cached. But for more complicated texts (f.e. if nice kerning needed) the interpreter has the option to cache the whole word. From rendering point of view there is no difference. Renderers will simply render items from a RenderQueue which they kept from caching and which basically contain only simple keys into the cache (and some control sequences like "\n"). In the case of single chars the queue will contain multiple keys, in case of nice whole words the queue contains only one item (f.e.). As I mentioned, I avoid using VBOs and therefore build the quads dynamically while rendering. (Seems to work quite well)

[GDI+] I found the reason why Linux was giving wrong results: On Windows the layout rectangle given to DrawString does not have to be initialized - on Linux it does. So this is working now. Thanks for the garbage suggestions!

[TextureManagers] I agree to prevent the user from registering any events here. The whole caching should be kept internally. So far I cannot see any real difference in our approaches. (My users only get a public property 'string Text' to set, its it)

Internally every text is cached. I somehow dont like the other approach to draw bitmaps directly but have not tested it really though. My requirements match the caching approach perfectly since I mostly have static text, many different fonts and the only dynamic parts are numbers may changing frequently.