浅析Label 的三种缓存模式

为了解决系统字经常打断合批的问题,cocos提供了三种缓存模式:官网文档
image
CacheMode.NONE: 先用网页canvas渲染文字,然后用Texture2D.initWithElement(canvas)获取纹理
CacheMode.BITMAP: 跟CacheMode.NONE差不多,区别就是会将实时获取到的纹理放到动态合集里,于是就能跟附近的同类型Label或者使用动态图集的Sprite进行合批。
***CacheMode.CHAR:***会先将label拆成一个个单独的字,每一个字都用以上的方法获取生成的纹理,放在一个专门存放此类字符纹理的大图内,然后渲染的时候使用多个顶点,按每个字符一个矩形渲染。

我们先来看下几种缓存模式下运行时的区别


如上图,None模式下,每个label都占一个drawcall,即使内容相同也不例外,此时内存中三个label纹理+一个sprite纹理+FPS label纹理。
Bitmap模式下,三个Label和Sprite共占一个drawcall,是因为三张label的纹理和sprite的纹理打到了一个动态图集里面。我们可以打印出动态图集:

let _debugNode = cc.dynamicAtlasManager.showDebug(true);
_debugNode.getComponent(cc.ScrollView).content.children[0].color = cc.Color.RED
_debugNode.scale = 0.5;

image

红色的就是动态图集,可以注意到即使是一模一样的Label,在自动图集里也不会共用贴图,有多少个BITMAP缓存模式的Label,就有多少份字体贴图,所以如果我们的游戏里有大量相同的label,更加适合用CHAR缓存模式。

再看CHAR模式,CHAR模式下相当于是全局共用一个2048x2048的位图字体:cc.Label._shareAtlas
我们打印一下该字体的纹理:

let shareLabelTex = cc.Label._shareAtlas.getTexture()
let spr = _debugNode.getComponent(cc.ScrollView).content.children[0].getComponent(cc.Sprite)
spr.spriteFrame.setTexture(shareLabelTex)

image
从上图顺便可以看出,系统FPS信息Label就是用的CHAR模式。

下面我们跟着Label源码来看看,几种模式分别是怎么装配渲染数据的
我们今天讨论的范围是非native环境下2d系统字。


从这段代码可知,CHAR模式下对应的Assembler是Letter,NONE模式和BITMAP模式的Assembler都是TTF。Label组件就是通过这些Assembler将字体数据变成纹理数据进而显示的,先看看他们是怎么关联的:

每次系统字相关设置发生变化,组件都会调用_applyFontTexture重新初始化纹理数据,非CHAR模式下,是直接创建一个新的Texture2D纹理,通过canvas初始化(红框部分)。
而每次诸如Label.string发生改变后,会更新渲染数据,让canvas重新绘制字体,从而使_ttfTexture得到刷新:

image
NONE模式和BITMAP模式的Assembler都是TTF,唯一的区别就是BITMAP模式下,会在生成完纹理后,将该纹理拷贝一份到自动图集,然后使用自动图集里面的内容。所以BITMAP的优势是能与周围使用自动图集的碎图一起合批,但同时也会加大自动图集的消耗。内容或字体大小等频繁变化的不要使用BITMAP模式,因为每刷新一次,就会拷贝一次最新纹理到自动图集。

关于CHAR模式,就有点绕了,生成纹理的方式还是一样,但不是 每个Label生成一个纹理,而是每个先把Label字符串分开,按每个字符去生成纹理,然后尝试拷贝纹理到共用的LetterAtlas大图,然后下次再遇到相同参数的字符,就直接取大图纹理。
image
上图中,红色的就是CHAR LABEL的公共纹理,可以看到,字体大小不同时,即使内容相同,也会使用不同的纹理内容。


所谓相同参数,就是上图的shareLabelInfo的color、fontSize、fontFamily、outline、margin,然后再与字符内容相加,得到的hash值,然后维护一个_letterDefinitions表,每增加一个新的letterTexture,就拷贝一份到公共大纹理,然后保存纹理坐标、宽、高等信息,hash为键值。

其实跟向动态图集插入纹理差不多,只不过系统字刚好有一些可以比较衡量的参数,所以比动态图集更方便的是可以判断某个小的字符纹理有没有被加到大图中,有的话就直接取。

然后再一点就是CHAR模式下,顶点数据有所不同,普通的Sprite或者Label,都是一个矩形、4个顶点、两个三角形,CHAR模式下,每个字符所用的纹理是不连续的,可能在大图中的任意位置,所以必须每个字符使用一套纹理坐标,有n个字符,就有n x 4个顶点。

3赞

赚了赚了,学到了

2.4.7 Label 已知问题:

1.BITMAP模式,文字会重复占用动态图集的BUG:
关闭界面就是隐藏,打开就是重新显示,没对关闭的界面进行销毁,重复打开几次后BITMAP模式的文字出现在动态图集里面有多个重复的,打开次数越多,重复越多。而且描边的文字一定会重复出现2个,每次打开就会多两个。

2.Android原生端,Label 勾选斜体或粗体会导致TTF无效的BUG,会变成默认字体。
3.Android原生端,Label 使用Char模式的字体会变得模糊粗糙。
4.Char模式下使用粗体、斜体、下划线时,效果不起作用(可能就是这么设计的)。

1赞

新之助大佬,我是阿呆啊 :stuck_out_tongue_winking_eye:

2.x的原生端TTF字体就压根不行

是吗?我这边试了的,只用不勾选斜体或粗体还是有效果的。

2.4.6试了吗?我这边试了没效果,安卓

2.4.6不确定,但2.x有好几个版本都有我说的这个问题。