一.cocos的内存管理方式
cocos是通过引用计数方式管理内存,主要通过两种方式实现
1.手动管理内存
当我们创建一个实例时,基类Ref里面有一个叫referenceCount的参数就会置1,它代表我们引用的次数,使用retain()函数
可以使refernceCount+1,release()函数会使referenceCount-1,当这个参数减至0时会被引擎delete掉释放内存
2.自动回收池
有手动就会有自动。我们在create()一个对象时,调用new函数创建对象并且还会利用autorelease()方法把该对象的指针加入自动回收池,主线程每一帧在Application::getInstance()->run()的过程中会调用 mainloop()方法
在mainloop()方法中会利用单例的PoolManager类getCurPool()获取当前的自动回收池,并调用自动回收池中的clear()方法,
clear()方法里,会遍历移除所有与该自动回收池相关联的对象,并调用一次release()方法,如果这个对象没被调用且referenceCount被减至0就会被delete掉。若这个对象已经addChild到其他控件上呗引用的话,则referenceCount不为0,等其父节点被清理时再回收
好处:
1.无需手动的release()和retain(),避免内存泄漏。
2.在每一帧结束后对无用的对象进行自动处理内存回收,方便可靠。
可优化的地方:
1.我们的PoolManager管理的是一个_releasePoolStack(存储自动回收池的栈),一般情况下,每一帧结束后我们只是把当前的池子给清空,然后执行下一帧的操作。但是我们也应该考虑到释放池本身维护着一个将要执行释放操作的对象列表,如果在一帧之内生成了大量的autorelease对象,将会导致释放池性能下降。因此,在生成autorelease对象密集的区域(通常是循环中)的前后,我们最好可以手动创建并释放一个回收池。
2.autorelease()只有在自动释放池被释放时才会进行一次释放操作,如果对象释放的次数超过了应有的次数,则这个错误在调用autorelease()时并不会被发现,只有当自动释放池被释放时(通常也就是游戏的每一帧结束时),游戏才会崩溃。在这种情况下,定位错误就变得十分困难了。因此,我们建议在开发过程中应该避免滥用autorelease(),只在工厂方法等不得不用的情况下使用,尽量以release()来释放对象引用。
二.cocos的渲染机制
3.x之前是通过调用每一个node的draw方法来使用OpenGL ES代码进行渲染,3.x之后则使用了新的渲染机制,统一把所有需要渲染的node安放在一个队列中再进行自动批处理渲染。
主线程每一帧在Application::getInstance()->run()的过程中会调用 mainloop()方法,mainloop()方法中会调用一次drawScene()方法
方法名是drawScene,那么重点肯定就是对当前场景需要渲染的结点进行渲染。先清除渲染状态,然后调用render()方法。
接下来我们看看render函数具体是怎么实现的:它首先是使用visit方法让需要被渲染的结点进行排序并插入到渲染队列CommandQueue中,然后再一起自动批处理进行渲染。
我们接着看visit方法,首先它根据localZOrder使用sortAllChildren()来进行排序
紧接着对localZOrder<0的结点进行递归渲染。
然后是渲染本身结点,最后再递归渲染localZOrder>0的子节点。其本质就是按照(左,中,右)中序遍历进行渲染
往下看每个结点的draw函数,我们可以发现,相比于2.x的版本,3.x之后没有直接在draw函数中进行渲染,而是把渲染命令压入到渲染对列中去,最后再回到render函数中进行进行自动批处理渲染
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags){ // 判定纹理是否有效 if (_texture == nullptr) { return; } #if CC_USE_CULLING // Don't calculate the culling if the transform was not updated auto visitingCamera = Camera::getVisitingCamera(); auto defaultCamera = Camera::getDefaultCamera(); if (visitingCamera == defaultCamera) { _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; } else { // XXX: this always return true since _insideBounds = renderer->checkVisibility(transform, _contentSize); } // 判定渲染的纹理是否在可见区域内 if(_insideBounds)#endif { _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); // 将绘制命令添加到renderer绘制栈RenderQueue中 renderer->addCommand(&_trianglesCommand); }}
在render中,对队列中需要渲染的结点根据其GlobalZorder进行排序,globalZOrder
是一个 float
(不是 int
)的参数。这个值在 渲染器
中用来给 RenderCommand
排序。较低的值拥有较高的优先级。这意味着一个 globalZorder
为 -10
的节点会比一个 globalZOrder
为 10
的节点优先绘制.globalZOrder
为 0
(默认值)的节点将会根据 Scene Graph 顺序绘制。
然后再对队列中的渲染命令调用OpenGL的API进行渲染。自此完成了整个渲染流程。
总的来说:
导演类的mainLoop中会调用drawScene,在drawScene中会调用场景类的render,render中会递归执行节点类的visit,visit中会调用精灵类的draw,draw中会执行渲染类的addCommand。对所有节点执行完addCommand后,会执行渲染类的processRenderCommand,接下来执行渲染类的drawBatchedTriangles,最终在drawBatchedTriangles内会调用多个o