Text and Complex Scripts

Started by
10 comments, last by Anon Mike 17 years, 9 months ago
I have done some researching about how to get text with good performance into an 3D Application, most of what I found just loaded all Glyphs of a Font into an Texture. But this doesn not seem usable if you think about i18n (internationalization) issues. Because there you got thousands of possible glyphs, complex script behaviour (right to left, glyph substitution, etc...). So i thought that i would maybe possible to render the text with OS functions (uniscribe, pango, ...) to memory and then store the text lines in textures. Is there any better approach to do this? I don't think rendering the text every frame is better. I am thinking about this in the context of an RPG where you got many text items (mob names, quest text, dialoges, tooltips, and much much more). Or does anyone know how Games like Guildwars handle that? I just seen a lot of korean, japanese and much more types of scripts there, so they got it working. Thanks, Fionn
Advertisement
I haven't tried the exact route you've suggested (doesn't sound like a bad idea though), but in a text-heavy application of mine I did end up finding that inefficient text rendering chewed up a huge percentage of my per-frame time.

Your suggestion of re-drawing every frame caught my eye - why consider this? Does the text actually change on every frame? If its being edited by a human then I'd be impressed if they regularly updated it at 30-60hz [grin]

Simply put - implement a form of lazy evaluation: only update the text texture (etc..) when it changes.

Draw all text as greyscale and you can then use geometry (e.g. modulate with a per-vertex or material diffuse) to add the colour. This is good if you want to animate/change colour without changing the text.

Also, if you did want to construct the text on your own - are you sure you'll need every possible symbol rendered to a texture at any one time? My aforementioned application was primarily the european languages and I had the advantage of being able to scan all the data files to build up a list of characters that were actually used. May not always work, but its a possibility - only add characters as/when necessary.

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Oh, one other thing while I'm at it [smile]

If you're using other libraries to render the text then you might wish to consider farming out updates to worker threads. You should be able to generate a block of pixels on a DC (or similar) without hurting your core frame rate. Copy the pixels over to the main thread for a well scheduled upload to VRAM.

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

I've never been a fan of quad-per-character text rendering. It basically only works for western scripts, it seldom gives you nice font handling (kerning, ligatures, etc), it pulls down frame rate on Intel Extreme hardware, and you pay the cost for each frame rendered.

What I recommend (and do myself) is to do what you say: render lines of text into a texture, and draw the entire text block at once. Typically, by rendering to system memory, and uploading to texture. To avoid switching textures too much, I typically allocate one texture (and thus one DC and one DIBSection) per text display area, to contain all text for the window. You can grow and shrink this texture, if the user re-sizes the window, or just settle on a maximum size for each window of text.

On Windows, you want to use CreateCompatibleDC(), CreateDIBSection(), and CreateFontIndirectEx(). Don't forget GDIFlush() before uploading the texture image! On Linux, you probably want to use malloc() and the FreeType library.

One last thing: To get alpha in the text, you might want to draw your text in white on black, and then do another pass of coloring on the text before uploading it, to support colored text with alpha. Another option is to know the background color, and derive the alpha channel based on how different the pixel color is. Because the text bitmap will already be in L2 cache after you've rendered it into the DIBSection or malloc()-ed memory, the coloring pass with the CPU will be very fast. And you only pay for it when the text changes.
enum Bool { True, False, FileNotFound };
@hplus0603:

So you have multiple lines of text in one big texture (maybe split the texture vertical into lines of the size of on line at a particular font size)?


I just thought that maybe a mixture of the quad based and the line based could do it.
I tought of splitting the lines also horizontally, e.g. 20 elements in a line. And then using something like a page table in the system kernel.

Just discover how many items a string would take up and find the next free sequence. If you run out of long enough seuqences you just could pack the existing texture or create a new one.
Because if you just use a one item per line it would waste much room of the texture. E.g. "The quest objective is to blabla..." compared to "Open", or just simple labels on the buttons.
An alternative to either a pre-creating a texture containing all font glyphs or rendering complete lines of text to a texture is to do something similar to how font systems in OSs tend to work - use a texture as a glyph cache. You draw glyphs into a texture as needed and keep a lookup table for the location in the texture of each glyph. This lets you handle complex character sets without needing to store every possible character in the texture in advance. The downside of this approach is that it requires the code that draws the textured quads for the glyphs to handle advanced layout tasks. The benefit is that it should be faster than rendering the complete string to a texture if the text is changing frequently - say if you have a chat window or logging window - since you won't need to upload a large chunk of the texture every time the text changes. Once the glyph cache is populated it should rarely need updating even with rapidly changing text.

For most games that don't need to update the text very frequently rendering the complete string to a texture and updating it only as needed is probably the best / easiest solution though.

Game Programming Blog: www.mattnewport.com/blog

The question is with the glyph cache thing if you not loose the performance gain with not rendering the strings with the transfer of the quads, because then you have to store the location of every single glyph and transfer this glyph image every frame.

And i believe that most text survives many frames (even with chat or log windows) - if you let a seperate thread do the rendering you should not have any problems i guess (for example i can also let a game run and some sort of irc client or such stuff in another window on my second screen smooth, which would be the same as running the rendering of strings in another thread).

And for long living text (like Player Names etc..) only a single transfer is needed instead of one for every glyph.

And if the system implements a glyph cache in it's system functions, rendering the strings would be very fast.
The reason for preferring the glyph cache is that texture uploads tend to be quite slow and you also risk causing GPU stalls if you are trying to update sections of a texture rather than the whole texture at once. If you use a glyph cache the texture does not need to be updated very often and drawing the quads is relatively cheap since you can use a dynamic vertex buffer with discard or no overwrite locks and avoid causing GPU stalls or transferring large amounts of texture data to the graphics card. You aren't transferring the glyph image every frame - the glyph images all live on a texture which you update as infrequently as possible. On modern GPUs drawing a few hundred quads in one draw call is not really significantly more expensive than drawing one so drawing a quad per glyph doesn't add much overhead.

The reason you don't have problems running other non 3D windows with text in at the same time as your app is that all Windows font rendering happens on the CPU, your 3D card isn't being used (unless you're running Windows Vista) and the expensive bit with transferring complete strings to the GPU is the texture uploads and associated stalls.

Game Programming Blog: www.mattnewport.com/blog

You could always do a hybrid approach: using a glyph cache to render to a text texture. That way, you get the advantages of not using quad-per-character, and the advantage of not having to send texture data to the card for new strings. I don't know how useful or fast that'd be, but it's an idea.
My approach (on Windows) is to create a DIB section, set the background text color to black, the foreground to white, render the text via ExtTextOut (or use Uniscribe if you prefer), post-process the bitmap to convert the greyscale output to alpha, and save it in a texture. With this method the system does all the hard work of complex string shaping and I can concentrate on moving bits around.

If you want to explore using a texture-based glyph cache you really have to use Uniscribe directly. The GDI functions to retrieve glyph indexes don't take into account things like font linking or EUDC which are required to display far-east text properly. Plus plain GDI doesn't have native support for surrogate characters at all.

I have not used Freetype but it appears to be just a rasterizer. This is a big piece but it's nowhere near the whole story if you want true internationalization. Correctly laying out a string in, say, Arabic is no easy task. English is pretty much the easiest thing out there, it only gets harder.
-Mike

This topic is closed to new replies.

Advertisement