Performance of Label rendering

I’m trying to improve the performance of a screen that is taking 2-3 seconds to load.
Basically most of the time is consumed while creating Labels for the numbers that you see on the right of the screenshot below.
The container is a scrollview and 18 rows are created (on the screenshot you only see 11).
Any tips on how I could speed up this?
I tried switching from TTF to BM fonts but saw no improvements?

how many drawcalls on ttf and how many on bm font?

If the draw calls didn’t improve drastically between TTF and the BM Font, then it’s most likely to do with the image you’re using on each row, since that would break batching of the BM font sprites.

There is a way to fix it by using the latest v3 or v4 branches on github. Once you check out the latest code, look at cpp-tests, file LabelTestNew.cpp, class LabelFNTMultiFontAtlasWithRotation. What it basically does is allow you to add your BM Font PNG file to a larger sprite sheet, so you would add it to the same sprite sheet as those small images you have on each row of your scroll view, and you simply change one thing in your *.fnt file, which is to reference the sprite sheet.

For example, say you are using arimo.fnt as your BM Font, with arimo.png as the bitmap font image file. You add the arimo.png to a larger sprite sheet (using something like TexturePacker etc); let’s call the sprite sheet SpriteSheet.png. Now you change the file field in the arimo.fnt file to reference the sprite sheet. It would look something like this:

info face="Arimo" size=40 bold=1 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
common lineHeight=40 base=32 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
page id=0 file="SpriteSheet.png"
...

To use it, you simply make sure your sprite sheet (PLIST) is loaded, and create the BM font label this way:

auto spriteCache = SpriteFrameCache::getInstance();
spriteCache->addSpriteFramesWithFile("SpriteSheet.plist"); // make sure the sprite atlas is loaded

auto label1 = Label::createWithBMFont("fonts/arimo.fnt", "Your Text", TextHAlignment::CENTER, 0, "arimo.png");
this->addChild(label1);

or alternatively, you can create it this way:

const auto frame = spriteCache->getSpriteFrameByName("arimo.png");
auto label2 = Label::createWithBMFont("fonts/arimo.fnt", "Your Text", TextHAlignment::CENTER, 0, frame->getRectInPixels(), frame->isRotated());
this->addChild(label2);

You can read more about it here: https://github.com/cocos2d/cocos2d-x/pull/20309

1 Like

how can I check the drawcalls? For each label you see on the screenshot I do a Label::createWithTTF and then add it to the scrollview.
With BM Fonts I use the Label::createWithBMFont

Director::getInstance()->setDisplayStats( "true" );

with TTF I have 169 calls and with BM only 40. However the BM rendering looks even slower than the TTF. My PNG is just 31Kb. I even tested by removing the sprite club logos, but made no difference.

When you say ‘rendering looks even slower,’ are you referring to a FPS drop? Or a delay in the initial render of the menu? If it is the latter, I would suggest caching a pool of Label objects early on in the app start up with the BM texture allocated, and then using those instead of create/alloc each one every time…

You could also go further and profile the app during this menu and see which allocations/gl calls are causing the delay or frame drops.

Also as a final tip – use TableView for these sorts of menus, not scrollviews. Tableviews only render cells that are on screen, and use smart recycling of allocated assets while scrolling. You can have a tableview with 100,000 rows be more efficient in allocations than the scrollview setup with 18 rows you have shown above.

it’s a 2-3 second delay in the initial render. I’ll try the tableView.

Out of curiosity, what was the draw call value when you removed the club logos? Was it still 40? If you use the new embedded BM font in the same sprite sheet as the logos, you should be able to cut it down to 2-5 draw calls for the entire scroll view. One draw call for the clipping, and another one for all logos/BM labels, and the other 2-3 would be for the scroll bar sprites if you use them.

This would also help reduce the draw calls with the TableView component.

Now I removed everything from the screen. only the labels are added. no images at all.
I started the stopwatch on the creation of the scene and stopped it on the onEnterTransitionDidFinish event.

Label::createWithTTF - 154 calls - 1.28 seconds
Label::createWithBMFont - 5 calls - 1.7 seconds

It takes a little time to allocate that many objects into memory especially with a texture attached. This is where pre-allocation is a smart solution.

During the app startup/load etc, you can pre-allocate Label objects with createBMFont and have the texture attached, store them inside a cocos2d::Vector<Label*>.

Now when you need one of those objects, instead of calling Label::createWithBMFont, just grab the next available node from the pre-allocated list.

There might be an object-pool class prebuilt in cocos you can look into as well.

it helps but not much. by using a preloaded vector of labels with createWithBMFont, time is reduced to 1.19 seconds.

Do you know how to profile the app? I would recommend looking which functions are consuming this time from here.

it’s all inside the textures of the label…

I even tried replacing my scroll control with a table view but made little difference.

playing around with actions I found an alternative to draw line by line, making the screen much faster to appear.
not exactly perfect, as it could be faster, but I think it’s ok. check it out… https://youtu.be/Ylnrkmpp3b4
any chance of reusing a label several times? as you see I have many duplicated numbers.

Have you disabled the creation of the labels to get a baseline reading of the time-span? Don’t disable any code around the labels, just the label creation/usage. Keep any code related to reading the data that is used to fill the labels in there etc.

If you really must, you can just avoid using the Label class altogether for the number display. Add the individual bitmap numbers 0 to 9 of the correct size (or bigger so you can scale down) into the sprite sheet. Make a new class that constructs the display value out of those numbers using sprites built from the 0-9 digit images.

The code would be considerably more efficient than the code path the Label creation uses, and relatively trivial to implement, including anything related to handling left/right/center alignment etc. You can call it something like “NumberMeter”, and you can literally use it anywhere else too, with different sized digit images, since the code can adjust based on the image sizes. You can also add effects on how the numbers change, for instance, like an odometer (rolling numbers etc).

I personally use something similar in my project, since I needed more flexibility than the Label class in how the digits are displayed.

1 Like