the Fiddler's picture

New text renderer

Project:The Open Toolkit library
Version:0.9.x-dev
Component:Code
Category:task
Priority:normal
Assigned:the Fiddler
Status:closed
Description

I have just committed a WIP implementation of a new text renderer to the text branch. Improvements:

  1. Simpler to use.
  2. More modular. It is now possible to have different GL1/GL2/GL3-level output or text layout providers. It is also possible to specify your own implementations (for example if you wish to integrate this into your engine).
  3. More accurate rendering (better grid fitting).
  4. Improved speed and reduced GC pressure through caching.
  5. Lower texture memory consumption.

The new implementation is based on GdiPMeasureCharacterRanges, which provides better control over text layout, when compared to MeasureString (the old implementation). Text is now cached on two levels, rasterized glyphs (loaded in texture memory) and laid out text. The public API looks like this:

// No TextureFonts, TextHandles or anything.
TextPrinter text = new TextPrinter();
[...]
text.Begin();
text.Print("Hello, World!", font);
text.Print(fps.ToString(), font, TextPrinterOptions.NoCache);
text.End();
[...]
TextExtents extents = text.Measure("ABC", font);
RectangleF bbox = extents.BoundingBox;
RectangleF char_pos = extents[1];  // Returns the position of "B" - useful for e.g. drawing a caret or selecting text.

The implementation in SVN is still under heavy development. Caveats:

  • (Fixed) Single texture sheet limitation remains. This is proving more difficult than expected.
  • (Fixed) Sometimes binds the wrong texture for glyphs (funky results!)
  • Trying to draw / measure uncached strings with newlines will cause GC pressure. This is the unfortunate result of a bug in Mono's GdiPMeasureCharacterRanges implementation. Fortunately, this is unlikely to cause problems in real life (you don't usually create dynamic text with newlines in a tight loop).

Anyone still interested despite these problems, you can test the code from the text branch. Expect screenshots shortly.

Edit: Clarified GC-pressure caveat.


Comments

Comment viewing options

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

#1

Single texture sheet limitation remains. This is proving more difficult than expected.

I believe I have run into this problem when making a font test that uses several different size fonts. It runs out of texture packing space and causes an exception.
While I have not looked at the implementation, would it be possible to determine to optimal size texture for a given font/decoration/size and pack only that font in that texture?
Most of the time the text rendering will be done on one font/decoration/size at a time and this would work great.
Just a thought.

Looking forward to checking this out when I have a chance.

the Fiddler's picture

#2

The old implementation tried to guess by looking at the size of the first font you render. If you are running into this limitation, try rendering some dummy text with the largest font first - this should work around the limitation.

The new implementation can allocate new textures as necessary. The problem is the actual text output: we need cut text into runs sorted by texture and I haven't been able to find a clean way of doing that.

Inertia's picture

#3

It appeared gdiplus.dll was missing, reinstalling .Net 2.0 didn't fix the problem. Is this intended to work on later .Net versions only?

Regarding texture sheets, there exists this Extension which seems to fit the problem. You could simply add an 1 byte index into the texture array per vertex. (I believe this is DX10+ hardware only though)

the Fiddler's picture

#4

No, this targets .Net 2.0. Let me see if I have a pure .Net 2.0 VM around here.

Interesting extension, this could solve the problem for new hardware. I'm not to sure about driver support (last update July 2008?) and without something like this, a sorting step seems unavoidable.

Inertia's picture

#5

Why sort at all? Restrict the TextPrinter to a single font, I think this is much better for both: the ease of implementation and the ability to debug blending problems when multiple pieces of text overlap. I.e. let the user sort by fonts&textprinter.

the Fiddler's picture

#6

That would not solve the problem, because a single font may be bigger than a single texture. Even if it isn't, you would be wasting texture memory.

It's not difficult to have multiple fonts inside a single texture. The problem occurs when a single text passage has glyphs on multiple textures. This information has to be communicated somehow from the glyph cache to the output provider. Current idea:

  1. The Print method iterates through every character and queries the glyph cache for information (texture and texcoords).
  2. Using this information, it places the characters in vertex arrays (one vertex array per texture).
  3. It binds and renders each vertex array / texture combo consecutively.

Anyone has a cleaner solution than this?

Inertia's picture

#7

Don't laugh, but why don't you bind Texture0 to TMU0, Texture1 to TMU1 etc. and use the 1-byte flag to index from which TMU the shader should sample? As far as I understood your code you're building the Texcoords on the fly anyways. Like this you could bind all textures in 1 go and don't bother splitting the characters into different VBOs.

I recall even my old Geforce FX allowed 4 samplers at once.

the Fiddler's picture

#8

Not laughing, just trying to see how this would work. Say you bind 4 textures and build a single VBO. How would you go about specifying which vertices refer to which texture? (Maybe this is a silly question, but my experience with multiple TU revolves the old register combiners).

haymo's picture

#9

Didnt have time to look at your code yet. But when I did my alternative text printer implementation, I decided to build a render item queue. It holds extended infos for every cached glyph. Would be rather easy to sort the items in here (which I dont do yet).

Maybe it would help also to put all glyphs of a single phrase/string into one texture sheet? While caching, one would have to rollback if the glyphs do not fit and start again on a new/next sheet. That way - at least - each phrase would come from one texture sheet...

[edit: clarified my sugestion to sort textures by phrases]

[Offtopic] Im sure this has been asked elsewhere - so please be patient: is it possible to get a notification on new comments from the forum software?

Inertia's picture

#10

Using a byte is already an optimization, the basic idea to fake a Texture3D is this:
Instead of a 2 component TexCoord, use the 3rd TexCoord (r) for indexing the textures.

r= 0.000 .. 0.249 -> TMU 0
r= 0.251 .. 0.499 -> TMU 1
r= 0.501 .. 0.749 -> TMU 2
r= 0.751 .. 1.000 -> TMU 3

It assumes that all 3 Vertices of a triangle always index the same texture.

[Vertex Shader]
varying vec2 TexCoord;
varying float r;
 
void main()
{
  gl_Position = ftransform();
  TexCoord = gl_TexCoord[0].st; // assuming Texture Matrix is identity
  r = gl_TexCoord[0].r;
}
 
[Fragment Shader]
uniform sampler2D Tex0;
uniform sampler2D Tex1;
uniform sampler2D Tex2;
uniform sampler2D Tex3;
 
varying vec2 TexCoord;
varying float r;
 
void main()
{
  if ( r > 0.5 )
  {
    if ( r > 0.75 )
      gl_FragColor = texture2d( Tex3, TexCoord.st );
    else // 0.5 < r <= 0.75
      gl_FragColor = texture2d( Tex2, TexCoord.st );
  }
  else // r <= 0.5
  {
    if ( r > 0.25 )
      gl_FragColor = texture2d( Tex1, TexCoord.st );
    else // r <= 0.25
      gl_FragColor = texture2d( Tex0, TexCoord.st );
  }
 
}

A good improvement would be moving the uniforms and conditionals into the vertex shader, but I don't think "varying sampler2D SelectedSampler" is valid. (feel free to try)