Create BMFont from TTF at runtime

Cocos2d-x’ TTF font rendering has a lot of features but it desperately lacks batch rendering. Using TTF fonts in a screen that displays lots of information or many tiny bits might give significant draw call (and performance) spikes.

Using Bitmap fonts it the general solution to that, but it’s not convenient to be limited to the characters that are shipped with the game. Player names might contain special characters or the game might need to be localization to a language with the Cyrillic alphabet / Chinese / etc.

Since Cocos2d-x TTF font rendering seems to be very powerful, it should be possible to render all characters requested by a certain language to a texture and just render the needed Bitmap font at runtime. If additional characters are needed, they could be added to the texture.

Has anybody ever realized something similar to that?
Could this feature be included in cocos2d-x?
If it’s not there yet, how could I tackle the implementation of this feature myself? It would be cool to get some insights from somebody who has experience with the internals of the current font rendering.

2 Likes

The TTF font rendering is done by first rendering glyphs into a bitmap (Font Atlas) using the FreeType library. The resulting bitmap is then uploaded to the GPU (or updated if it already exists). Offsets into the texture for each char are calculated to be used for atlas rendering.

If you change the text of a TTF label, cocos checks if the font atlas used by this label contains every char in the new text. If it doesn’t, all new chars are appended to the font atlas by rendering them at offsets into the bitmap.

So what you mentioned is indeed already done :wink:

To sum it up, TTF fonts are basically rendered like bitmap fonts (in theory). What seperates them is that bitmap font labels are drawn by using TriangleCommands, thus making them available to the Renderer's auto-batching, while TTFs are rendered using AtlasTexture's drawQuads function. The drawQuads function is using GL commands directly (hence it must be used inside a CustomCommand, which it is in this case), bypassing all external batching logic.

To fix the issue that TTF labels are not batched, you have to change the rendering method from AtlasTexture's drawQuads to using TriangleCommands.

I once did this myself (back in cocos2d-x v3.8 I think), by adding a drawQuadsToTriangleCommand function to TextureAtlas, which essentially just wraps a TriangleCommand around the TextureAtlas data, and then used this in the Label's draw function. The tricky part was that since the Renderer works defered, it needed a pointer to a TriangleCommand. Since more then one TriangleCommand could be emitted (in case of multiple atlases) per draw, some pooling functionallity was needed to keep track of used TriangleCommands and to avoid memory leaks and reallocations.

However, my fix was kind of hacky and not future proof :stuck_out_tongue:.

Regards,
Darian

1 Like

@Darinex

What you say there sounds awesome actually. But let me ask some more questions, so I fully understand:

  1. If I crate two TTF fonts labels with the same TTF fonts (and same settings for glow and what not), will they both share an AtlasTexture?
    I’ve tried for so many hours to get behind the TTF rendering, but the whole rendering is just so complex. Any insights that might lead to a batch rendering fix are welcome :wink:

  2. Are glows, shadows and outlines of TTF fonts also rendered to the texture? Could they also be batched by your fix in 3.8?

  3. With your fix, what did you have to do to be able to batch render TTF fonts? Did you create a TTF label with a huge set of needed symbols? Or did it just work by using TTF labels like Bitmap labels then?

  4. Would you be willing to share your fix code from 3.8? I would love to transcript it into 3.13 and make it work again (if it doesn’t already :slight_smile: )

Best,
Max

Hey Max,

let’s see if I can adress all your questions:

1.) In order for two TTF fonts to share the same AtlasTexture, they need to have the same font size, outline size, filename and the same DF (Distance Field) setting. If all of these conditions are fulfilled, then they use the same AtlasTexture

2.) Only outlines are rendered to the texture. And no, my fix sadly wasn’t able to handle glows and/or shadows. This is because they rely on using uniforms in the shaders.

In the current cocos2d-x implementation, all TTF labels with the same effects share the same GLProgramState (this is because the program state is fetched by using GLProgramState::getOrCreateWithGLProgramName, which is just returning a cached GLProgramState for the same program name). Since the glow and shadow effects rely on special uniforms, and are using the same program states, this means that they are not applicable for the defered TriangleCommand, since all uniform changes to the program state would alter it for all labels.

3.) For every TTF label, I just put all the data that would be rendered by AtlasTexture, into a TriangleCommand and send it to the Renderer. This way the Renderer takes care of the batching

4.) Sure :slight_smile: . Problem is, I don’t have access to the code right now, since I’m not using the my usual workstation. When I get back to my usual workstation, I will post the code here :smiley:.

Regards,
Darian

Hey @Darinex ,

Thanks for the in-depth reply. That helped, but I’m going to keep asking a bit :wink:

  1. Is Cocos2d-x really able to create real Distance Field fonts at runtime and do they work fine? Isn’t it needed to render them at a very high resolution and then downscale them? Wouldn’t that take quite long at runtime? When using DF-fonts, is it possible to adjust the font size and still use the same texture? Or would one need to scale the label for the distance field effect?

  2. I see, but I guess with your Advanced Renderer from the other thread it should be possible to create some shaders that do the trick and pass the glows/shadows wanted as attributes. I was trying to achieve exactly this (outlines/glows/shadows) with shaders when I realized that Cocos2d-x is always using the same shader program and only able to set uniforms. Then I asked the other question about shaders and attributes :wink:

  3. Looking forward to it!

1.) I don’t know what you mean by real Distance Field fonts :stuck_out_tongue: . As you mentioned, Distance Field fonts are usually generated from high-resolution binary images (each pixel is either ‘on’(1) or ‘off’(0)), however it seems like cocos skips this part and generates the distance field map using a different method.

For me, DF fonts always worked fine. I’m not quite sure about the runtime performance. The creation function do looks very complex.

Changing the the font size requires a new atlas being generated, possibly purging the old atlas’s data if it is no longer referenced by anyone. To get a ‘bigger’ text you should scale the label, since this is exactly what DF fonts are made for.

Yes, that should be possible. It’s generally a good idea to put data that differ often per sprite/geometry into attributes instead of uniforms, since uniforms make batching alot harder (at least with cocos2d-x’s current rendering pipeline).

Ah okay, the distance fonts that I’ve seen use alpha or black/white values per pixel:

I implemented this in Cocos2d-x with a shader and bitmap fonts.

I was hoping that the Cocos2d-x TTF fonts would allow to set the font size with something like the “bmFontSize” of the bitmap fonts. Kind of a virtual font size that scales the label internally.

Yes, it’s time for a new rendering pipeline for Cocos2d-x for sure :wink:

Do you have any idea when you’ll be able to post your TTF font code? Then I’d probably do a combined sessions of trying out the new renderer and TTF rendering - diving fully into the low level stuff.

Also I wanted to ask you if you’d be interested in joining our team (probably as an intern in the beginning). Of course, we’d need to talk on skype first and ideally also meet in person (if somehow possible) to find out if we both would like to work together. You definitely sound like a very nice guy and a talented coder. Also we’re a remote team anyway, so you don’t need to relocate and could work while watching boring lectures in Berlin :wink:

So, here are the files I changed to make the TTF batching work (cocos2d-x v3.8.1). Keep in mind that this “fix” was done in a write-and-forget manner, so it is kind of messy and not well written. However it got the job done. Also the batching only works for non-distance-field TTF fonts. All other label effects are not batched.

The whole thing could be done alot better and also for all label effects, but that would require you to pretty much rewrite large parts of the Label class.

In the code there are some occurrences of FastPool and FastVector. You can replace them by any pool/vector class you like (they don’t serve any special purpose).

ccShader_Label_normal.frag.h (1.5 KB)
ccShader_Label.vert.h (1.6 KB)
CCTextureAtlas.h (9.5 KB)
CCLabel.h (19.8 KB)
CCTextureAtlas.cpp (19.6 KB)
CCLabel.cpp (44.4 KB)

Remove the .h extension from the shader files. I had to put them there since .vert and .frag are no valid upload file extensions on this site.

Regards,
Darian

3 Likes

may be the shadow’s color and outline’s color is wrong.


I fixed the shadow effect and the same color outline effect.
The different color outline effect doesn’t batched.

1 Like

hi, how do you fixed the shadow effect ? could you share your code ?
@tiffanyx

Can you give me 3.17 version code?
@Darinex

It hasn’t changed much perhaps just the way you instantiate. Also look in cpp-tests

what means??

Anw way a cocos2d-x developer can improve this code and add it as actual feature in next cocos2d-x c++ version? Its a very nice feature for us to have, will improve the perforamcne of our games greatly. BMFonts are limited if we have multi-language support.

@tiffanyx If you’re still active on this forum, and you’re willing to share the modifications you made in order to get the shadow effect and color outline effect to work in Darinex’s example code, then we would all greatly appreciate it.

My codes is base on cocos2dx v3.10 and modified a lot of codes.
I’m sorry. I can’t publish the complete code.
CCLabel.cpp (44.3 KB)
CCLabel.h (15.4 KB)
git diff.zip (22.4 KB)

Thanks tiffanyx, what you posted will be helpful.