OpenGL fonts, what is the best way to go?

Started by
28 comments, last by Roots 19 years, 3 months ago
Try Here

Here's something I knocked up which should probably mean I'll never have to write another text rendering utility ever again(as long as I stick to OpenGL). It loads the glyphs dynamically, so(with error trapping aside):

fontUtil->GetFace("Tahoma")->Select(12)->RenderText("Hello World",GLFonts::DefaultOutputOpts);


The tahoma face will be loaded automatically if it doesn't already exist, then you select which point size you need, then simply the text you wish to render, with output operations to select italicized text, alignment, etc). It still needs a bit of cleaning up as it stands, but I hope it's some help :)

The calling for rendering might seem a bit lengthy, but a quick wrapper for selecting a default font shouldn't take more than a couple of minutes to knock up :)

Also note you can use friendly font names, like "Courier New", instead of locating the TTF file yourself, you can also type in the filename if you so wish(in the case of having custom fonts you distribute with your app). Only problem with this is, that this feature is Windows only, it's easily removable from the rest of the text output util, though.
Advertisement
Quote:Original post by nife
Quote:Original post by Koshmaar
For some games that would be true, but in my case - no, it can't be done. Not to mention GUI, where each control can have it's own font type, font size, font color, font attributes etc.


You're right, my method is only good if you're using 1 size and 1 attribute for each font (which is what I'm using). The color can always achieved by real-time coloring..


You could use a simple caching scheme; its unlikely that most gui's are going to want more than one font in perhaps a couple of sizes (I mean, look at windows or a desktop linux [without a word-processor open, that's cheating!])
What about unicode-enabled font rendering functions in OpenGL? I looked thru here, OpenGL Font Survey, but I saw nothing that either confirmed nor denied that unicode does/does not work with any of these font renderers. I'd really like to have unicode support in the game I'm writing right now, and we're using SDL so SDL_ttf is always an option, but I'm concerned about the overhead costs in converting the SDL font surfaces to OpenGL textures. (I don't have an extensive graphics background btw). We were planning on going with FTGL until we found out that it doesn't mention anything about Unicode. Thanks!

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

Quote:
but I'm concerned about the overhead costs in converting the SDL font surfaces to OpenGL textures. (I don't have an extensive graphics background btw).


If you are doing it every frame, then even for small texts it's gonna bite you for no good! Ie. I've coded little application using my engine, just black screen and nothing more. It run at 70 fps (with vsync enabled), stats were displayed at upper window border. Ok, I've added 2 strings that would display coordinates of mouse cursor, ie. "122 243" etc. It was done with theese functions:

void SC :: Renderer :: RenderText(const char * _text, const char * _fontName, usint _size, const TScrPoint & _position,                     uchar _r, uchar _g, uchar _b, ulint _style, eTextPosition _justify, eTextRenderMode _rMode) {       	          TextObjHandle renderedText = CreateTextTexture(_text, _fontName, _size, _r, _g, _b, _style, _rMode);           TScrPoint pos (_position);        if (_justify == tp_Centered)  // center text     pos.x = pos.x - (renderedText.width)/2;         if (_justify == tp_Right) // justify to right          pos.x = pos.x - renderedText.width;         boundTexture.id = renderedText.id;    boundTexture.width = PowerOfTwo(renderedText.width);    boundTexture.height = PowerOfTwo(renderedText.height);           	    RenderTexture(pos, renderedText.width, renderedText.height, 1, TScrPnt(0,0), renderedText.width, renderedText.height);        FreeTextObj(renderedText); }SC :: TextObjHandle SC :: Renderer :: CreateTextTexture(const char * _text, const char * _fontName, usint _size, uchar _r,                                                         uchar _g, uchar _b, ulint _style, eTextRenderMode _rMode){ TTF_Font * font = getFontMgr.GetFont(_fontName, _size); TextObjHandle textObj; textObj.id = 0;    if (!font)   {    logError2("Renderer", "Couldn't find font to output text:")    return textObj;   }      TTF_SetFontStyle(font, _style);       SDL_Color clrFg = {_r, _g, _b, 0}; // color of text       SDL_Surface * tmp;          if (_rMode == trm_Solid)       tmp = TTF_RenderText_Solid( font, _text, clrFg);   else if (_rMode == trm_Blended)    tmp = TTF_RenderText_Blended( font, _text, clrFg);      if (tmp == 0)    {     logError2("Renderer", "Couldn't create texture for rendered text")     return textObj;       }       // -------------------------------------------------    // OpenGL need to use the surface with width and height expanded to powers of 2    usint w = PowerOfTwo(tmp->w);	usint h = PowerOfTwo(tmp->h);							SDL_Surface * image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,	0x000000FF,	0x0000FF00, 0x00FF0000, 0xFF000000);		if ( image == 0 ) 	 {      logError2("Renderer", "Couldn't create texture for rendered text")	  SC_ASSERT(0)	  return textObj;	 } 	     // copy the tmp into the GL texture image    SDL_Rect area;	area.x = 0;	area.y = 0;	area.w = tmp->w;	area.h = tmp->h;		SDL_BlitSurface(tmp, &area, image, &area);	 	      // create an OpenGL texture for the image 	glGenTextures(1, &textObj.id);	glBindTexture(GL_TEXTURE_2D, textObj.id);	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);   	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);		     	           // set texture for use    textObj.width = tmp->w;    textObj.height = tmp->h;        SDL_FreeSurface(tmp);    SDL_FreeSurface(image);        return textObj;}


As you can see, it's SDL_TTF & OpenGL version (sure, it's crappy and not optimized, but IMHO nothing great can be achieved by optimizing theese, because slowliness is caused by external code and hardware limitations). So every frame RenderText was called twice. And FPS fall to 65 - that's 2,5 frames for every rapidly changing text. As you can see, cost is really high :-/
Also, I've tested whether it was fault of dynamic texture creation or some screwed OpenGL code in RenderFont, so I've used this:

struct TextObjHandle    {     GLuint id;         ulint width, height;    };  bool SC :: Renderer :: CreateTextObject(TextObjHandle & _handle, const char * _text, const char * _fontName, usint _size,                                          uchar _r, uchar _g, uchar _b, ulint _style, eTextRenderMode _rMode) {  _handle = CreateTextTexture(_text, _fontName, _size, _r, _g, _b, _style, _rMode);            return (_handle.id != 0); }    void SC :: Renderer :: FreeTextObj(TextObjHandle & _handle) {   glDeleteTextures(1, &_handle.id);       _handle.id = 0;   }     void SC :: Renderer :: RenderText(const TextObjHandle & _handle, const TScrPoint & _position, eTextPosition _justify) {       	                     TScrPoint pos (_position);        if (_justify == tp_Centered)  // center text     pos.x = pos.x - (_handle.width)/2;         if (_justify == tp_Right) // justify to right          pos.x = pos.x - _handle.width;             boundTexture.id = _handle.id;    boundTexture.width = PowerOfTwo(_handle.width);    boundTexture.height = PowerOfTwo(_handle.height);    glBindTexture(GL_TEXTURE_2D, _handle.id);                                   RenderTexture(pos, _handle.width, _handle.height, 1, TScrPnt(0,0), _handle.width, _handle.height);      }


Created 2 static (non changing) strings, erased old ones and tested again. And you know what? It was 70 FPS :-/

In this situation you'll realize that for any serious project that uses fast changing texts with SDL_TTF & OpenGL, Bob Pendelton's solution that I've posted here before is really neccessary (obviously I will use it in my engine).
Koshmaar: Not to be mean or anything but your test is really flawed, first testing with v-sync on is generally bad, The difference would have been huge with it off.

And it looks like you didn't use glTexSubImage2D, which is a MASSIVE optimization for this (the glGenTextures is the real problem). I went from ~16ms per frame to ~1.6ms per frame (or 60fps to 600fps).

I use ms for performance, because fps can be a misleading way to find the cost of functions, for example the difference between 1200 fps and 900 fps is the same as 60 fps and 59.016 fps. So if you write some cool function earlier in you project that's called once per frame and doesn't depend on the complexity of the scene (fps counter is a good example), but you decide to drop of it because it cost you 300 fps (when your app runs at 1200 fps), then late in your project you try to remake it and it only cost you 2 fps (when your app now runs at 60 fps), you in fact wasted your time making a slower function and would have been better off keeping the first one.

I'll say texture map fonts are probably faster in most cases and definitely easier to use efficiently. Although with a huge block of static text, SDL_TTF's ability to do it in 1 quad could be better than texture fonts, but that's an unlikely case in a game. But the cost of SDL_TTF isn't really that bad if you tweak it right (I gave 0.23852ms for a fps counter), but tweaking it is tricky, glTexSubImage2D is the biggest improvement, also huge font sizes can be become costly.

As for the Unicode thing I know SDL_TTF can (In spite of my compiler not being setup for Unicode I did get it to print out some Japanese fonts from a ttf file). And for full Unicode normal texture fonts could be be costly in texture memory. For example 2^16 different fonts at 16x16 would be 16777216 texels, for a 8 bit (for alpha blending) texture that is 16MB for one font map (64MB for 32x32 fonts, 256MB for 64x64 fonts).

From reading some of this posts I'm thinking that using a texture for a font cache from the SDL_TTF rendering function (using glTexSubImage2D to update it) could be a good system (the cost of large font sizes would be low), the tricky part (that I can see) would be handling the varying width's (ie the cache could become fragmented), and how to handle font changes (one texture per font or one texture for the system). It would loss the the 1 quad per text block of SDL_TTF (I don't think it's that helpful), and could gain a fair speed boost (I'm assuming the time for the render function is a linear to the length of the string, ie rendering "abcd" takes 4 times as long as just "a"), but you would get all the other advantages of SDL_TTF.
First, it wasn't meant to be accurate benchmark, just a little test program that shows you how costly it could be to use SDL_TTF with OpenGL and creating OpenGL textures from SDL_Surfaces on the fly, as Roots asked. Also, I did checked FPS count without vsync (around 120-124) with blank screen and when rendering two strings - around 102-3 FPS and with one string - around 109-111 FPS. Now it's clear that with such low framerates cost of using brute force methods was very costly (and that's what I tried to prove in my previous post).
Btw, I forgot to mention that every frame glClear was used; without it, no text drawing etc. I got around 130 FPS; I also didn't make this clear enough that all tests were done in windowed mode.
As for glTexSubImage2D - one day I'll try this with Bob Pendeltons solution, in fullscreen, with vsync turned off and I'll profile it with Vtune; I wonder what will I get, ie. maybe rendering text will be actually faster then not rendering it? ;-) (to render or not to render, this is a question :->)

Quote: and how to handle font changes


I used system in which user must first register font name in FontMgr and pass along with it vector of ints that contain all possible font sizes this user will want to work with it. Then, when RenderText was called, Renderer would ask FontMgr whether this font with such font size exists, and if so, then return pointer to it.
I think I found something promissing [smile]


FreeType2 Font Library

Some features:
- Open Source License
- Cross-Platform
- Supports Unicode and a whole crapload of other font types
- Anti-aliasing, etc.


Anyone have any experience using it with OpenGL?

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

Well, FreeType is library over which SDL_TTF was built ;-)
I heard that FreeType is big, complicated and it's hard to write anything with it. So thought Sam Lantinga, who decided to make SDL developer's life easier, and write SDL_TTF.
IMHO if you're using SDL (or SDL + OpenGL) then there's really no need to design, write, debug etc. another wraper for it, cause it's all done for you in SDL_TTF. In case you're using sth else... you could try, but I do believe that for other libraries (!SDL at least) are other, already written, good and tested text rendering libraries.
Quote:Original post by Koshmaar
Well, FreeType is library over which SDL_TTF was built ;-)
I heard that FreeType is big, complicated and it's hard to write anything with it. So thought Sam Lantinga, who decided to make SDL developer's life easier, and write SDL_TTF.
IMHO if you're using SDL (or SDL + OpenGL) then there's really no need to design, write, debug etc. another wraper for it, cause it's all done for you in SDL_TTF. In case you're using sth else... you could try, but I do believe that for other libraries (!SDL at least) are other, already written, good and tested text rendering libraries.



I've played around a little bit with SDL_TTF a few months ago, but not a whole lot. The reason I was looking for something else is that I wanted my text renderer to support Unicode so we could create versions of the game in Japanese, Korean, etc. if we wanted to. I'm a very open-source, open-world kind of guy. [grin]

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

*bump*

Anyone? Experience with FreeType2 or another font rendering technique/library for unicode?

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

This topic is closed to new replies.

Advertisement