性能优化建议

希望各位 注重下性能(特别是CPU)优化部分.
举个栗子:
目前的渲染系统基本框架是有许多比较严重CPU性能问题的.

Scene::render()
{
for (const auto& camera : getCameras())
{
//矩阵运算(略)
//camera相关操作(略)
visit() //全节点递归visit 构建渲染指令
render() //渲染
}

}
上面的部分暴露出两个严重问题

问题1:
假设现在两个camera A B,1000个子节点,其中999个被A所见,1个被B所见.
visit却需要执行2000次,这是严重不合理的.

问题2:


Github issue #16100 这不是优化,是个bug
visit传入父MV矩阵的目的是为了最终计算出当前节点的相关矩阵,然后用于setUniformsForBuiltins.
那么为何不选择分开ModelMatrix 和ViewMatrix?
在实际调用draw之前才计算递归父ModelMatrix(直到_transformDirty为false)
然后让用户自行决定M V是放到GPU计算还是CPU计算.

现在的游戏体量越发大了,也许小游戏 看不出什么,但梦想总是要有的
Show一张在研图(基于Cocos的3D无缝大世界编辑器[3600万平方米] )


2赞

不明觉厉

加油!引擎组大佬们一起探讨一下

确实,camera有几个就可以看出来性能不行

性能優化的問題永遠視而不見,因此cocos永遠不能到達unity, unreal的水平,現在還有godot後來追上

他们人手不足,也没办法,毕竟已经开源&免费,常怀感恩之心吧.

这个问题我已经解决了.基本不用GitHub,考虑到用的人也不多,论坛回一下吧.

首先纠正一个错误.
问题1确实存在.解决后visit部分性能提升倍数与camera数量成正比.
问题2描述有点问题.解决后对于不动的对象,省去了大量递归矩阵运算.

问题1的解决办法是
Node::setCameraMask方法中,遍历场景camera ,依据Flag和Mask 针对此对象是否可见,将其从camera的_nodeVisibleVector 中进行 添加 / 删除 . 对应构造和析构的地方需要对应处理下.

然后在Scene的Render中.将visit() 修改为 遍历 camera->_nodeVisibleVector 逐个visit .最后 调用 renderer->render();

接下来需要修改Node->visit函数,不再遍历访问子节点,

这样处理后,对象节点的visit就完全不会因多camera造成额外损耗了.

问题2:
和问题1有关联, 在visit 中 原需要获得父节点的世界矩阵,并依据此计算当前对象的世界矩阵. (cocos代码中_modelViewTransform 名称和我的习惯有出入导致我误解了)

这里我删除了此参数. 将getNodeToWorldTransform()函数进行修改, 加入脏数据缓存机制,获得当前对象的世界矩阵直接访问缓存即可,若为脏则再进行计算. 由此一来,性能大幅提升.

另外删除了2D物理运算部分.(不知为何其默认是启用的,且消耗占比很高)

解决此问题后,visit消耗部分降低了数倍.drawcalls 能力亦间接提升(静态对象越多,提升越大).
(drawcalls开销本质是在CPU-GPU通信上,此处降低了大量CPU开销,drawcalls自然可以允许更多次调用)

提示:
如果你只有一个camera,改动1无意义.
但是改动2效果具大

1赞

我是因为失望,因为我也提出过几次优化提案,在这里,在英文论坛,在github上,结果最后都是无视或不了了之。

说回你的解决方法,我不是很明白改动2的意义,原本的代码在计算世界矩阵上已经有脏数据缓存机制:

__世界矩阵__是这么计算的:

if(flags & FLAGS_DIRTY_MASK)
        _modelViewTransform = this->transform(parentTransform);

可以看到在一次processParentFlags里,如果自身至rootscene的所有节点都没有dirty,那么_modelViewTransform是不会重新计算的

然后,对父矩阵是这么计算的:

const Mat4& Node::getNodeToParentTransform() const
{
    if (_transformDirty)
    {
       ...
    }

    if (_additionalTransform)
    {
       ...
    }

    _transformDirty = _additionalTransformDirty = false;

    return _transform;
}

因此自身_transformDirty是false的时候,_transform也是不会重新计算的。你可以看到,脏数据缓存机制已经存在

嗯… 我大概看了下,你是对的,但是还是感觉原版有点绕.所以全改啦:joy:
原来的visit需要传递_modelViewTransform,
主要流程是

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{ 
    uint32_t flags = processParentFlags(parentTransform, parentFlags);
    _director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    _director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

   if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);

   for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}

改动以后就变为: (因为Scene部分已经不再需要遍历子节点visit了)

 void Node::visit(Renderer* renderer, uint32_t parentFlags)
 { 
	_modelViewTransform = getNodeToWorldTransform();
	if (visibleByCamera)
		this->draw(renderer, _modelViewTransform, flags);
 }

以及

Mat4 Node::getNodeToWorldTransform() const
{

	if (!_nodeToWorldTransformDirty)
	{
		return _nodeToWorldTransformCache;
	} 
	_nodeToWorldTransformDirty = false;
	_nodeToWorldTransformCache = this->getNodeToParentTransform(nullptr);
	return _nodeToWorldTransformCache;
}

原先Scene::render是

for (const auto& camera : getCameras())
{
     Camera::_visitingCamera = camera;
     this->visit(...);  //会一层一层遍历所有节点,哪怕某节点并不会被此相机看到
}

现在替换为

for (const auto& camera : getCameras())
{
	Camera::_visitingCamera = camera;
	for (size_t i = 0; i < camera->getCameraChildren().size(); i++)
	{ 
		camera->getCameraChildren().at(i)->visit(renderer, 0);  // 此函数经过改动后仅Visit此节点自身
	 }
}

Camera::getCameraChildren() 是我为了修改问题1而新增的,它仅在Node::setCameraMask的时候变动(包括构造析构的时候)
这样就不会浪费性能在大循环上了,可以确保每个对象仅对相机可见的时候才会被执行到visit.

能给我看看吗:grin: 我现在在做一个项目是3D大世界场景(like荒野之息),对性能要求相当高,希望增益改动多多益善.

我看不太明白你为什么要从Node :: visit拔掉遍历子路由器的功能,parentFlags要怎么传递给他们?

我给cocos团队建议过的,都要官方去做,例如使用bgfx渲染库,我们是不应该去触碰的,顶多搞些小魔改

为何使用cocos而不用unity去开发3D项目?另外可以帮你节省很多时间吧。我只会用cocos开发2D项目

这里面其实细节挺多,我只是抽了重点的部分描述了下.
因为我的项目场景里基本上都是数千个节点存在,同时还有多个相机.(默认的处理UI等、用于阴影、用于场景AB的.)
做性能热点分析的时候发现,CPU一般30~50%是消耗在visit里的.回过头来一看,不得不如此修改了.

bgfx 我还是刚刚听闻, 简单搜索了下,似乎是一个很好的东西,值得一试![quote=“ichinfungi, post:11, topic:90248”]
为何使用cocos而不用unity去开发3D项目?
[/quote]
大世界这种 unity性能出了问题完全不知道咋办了… 测试了它自带的terrain系统实在有点尴尬.
虽然cocos这部分那是相当少,不过好在可以自己写.足够简单,足够性能.它帮助解决框架、2D和跨平台的许多问题,已经节省了很多时间了.
看看在研的样子
http://www.icarus-li.com/video/d1.mp4

2赞

大作啊,牛逼!希望官方能重视问题啊

看着非常不错啊

看起来很棒,大作大麦

bgfx渲染库 期待官方能这样做

这个看着真的牛逼

看得我都想玩了

视频看不了,这是什么问题

请问能提供下这个文件的源码么