cocos creator 2.1.1 性能优化之 draw call优化
一.原理
1.优化draw call的原因
CPU与GPU在进行通讯时,CPU会将准备好的渲染数据存储在命令缓冲区中(Command Buffer)然后由GPU从中读取数据并进行渲染。通常GPU的渲染速度非常快,渲染几百个和几千个网格没有什么太大的差别,但是过多的draw call意味着CPU需要做的准备工作就越多,从而导致CPU的效率降低。其次在PC端CPU与GPU在进行交互时需要跨越硬件交互,而为了保证跨越硬件带来的数据同步问题,又会导致效率低下。所以CPU是draw call性能问题的元凶。
2.如何优化
根据优化原因可知,我们需要降低CPU提交draw call的数量。cocos creator中有动态合批处理,会自动将碎图动态的打成一张大图。但是限制是单张图片的最大尺寸不能超过512x512,最小尺寸要大于 8x8。如果图片的尺寸超过了合集的要求,我们就应该在节点的摆放上做一些处理了。
let _maxAtlasCount = 5;
let _textureSize = 2048;
// Smaller frame is more likely to be affected by linear filter
let _minFrameSize = 8;
let _maxFrameSize = 512;
...
...
/**
* !#en The maximum size of the picture that can be added to the atlas.
* !#zh 可以添加进图集的图片的最大尺寸。
* @property maxFrameSize
* @type {Number}
*/
get maxFrameSize () {
return _maxFrameSize;
},
set maxFrameSize (value) {
_maxFrameSize = value;
},
/**
* !#en The minimum size of the picture that can be added to the atlas.
* !#zh 可以添加进图集的图片的最小尺寸。
* @property minFrameSize
* @type {Number}
*/
get minFrameSize () {
return _minFrameSize;
},
set minFrameSize (value) {
_minFrameSize = value;
},
二.项目分析
1.cocos creator 中的自动合批
将两张碎图放置在场景中,并且最大值不超过512x512,如果自动合批起作用,在运行时应该会有2个drawcall(显示drawcall的面板也会占用一个drawcall)
运行结果满足预测。
当加入一个大于512x512的图片资源,并且节点摆放位置在最上层时应该会有3个draw call,图片如下
运行结果满足预测。
2.节点摆放对draw call的影响
实验:
- 如果我们将无法打入合集的节点放置在能打入合集节点的中间,是否会打断节点从而增加draw call呢?
结论:由图可知drawcall变为了4,所以同样的节点经过不同的摆放是会增加drawcall消耗的。
3.项目优化
1.drawcall的优化并不意味着一定要纠结于节点的摆放,而是应在不影响层级显示及后续维护的情况下合理的摆放节点。以多个不同的fnt字体举例,在摆放节点时,为了方便程序维护节点,我们可以不纠结于字体是否会打断合批,而是在运行时动态管理节点。将采用相同fnt字体的组件放置在同一父节点下,然后控制不同fnt父节点的层级位置来达到减少drawcall的目的。
2.示例代码:
//获取content节点下的所有label组件
let labelArray = content.getComponentsInChildren(cc.Label);
let labelType = [];
let labelFont = [];
if (labelArray) {
labelArray.forEach(element => {
//如果该字体资源是fnt资源则对其进行分类
if (element.font) {
//如果labelType中未存储此字体资源则创建一个node节点用来放置所有同类型的字体节点
if (!labelType.includes(element.font.name)) {
labelType.push(element.font.name);
let tempNode = new cc.Node();
tempNode.parent = content;
labelFont.push(tempNode);
let pos = tempNode.convertToNodeSpaceAR(element.node.parent.convertToWorldSpaceAR(element.node));
element.node.parent = tempNode;
element.node.position = pos;
}
else {
//如果已创建了node节点则将其丢进与之对应的节点下
let index = labelType.indexOf(element.font.name);
let pos = labelFont[index].convertToNodeSpaceAR(element.node.parent.convertToWorldSpaceAR(element.node));
element.node.parent = labelFont[index];
element.node.position = pos;
}
}
})
}