8月1号这一天,cocos2d-x 官方微博上说 x 团队正在纠结是否用智能指针替换现有的引用计数内存管理机制,结果引发大家的争论。
在我看来,引发这个争论的原因是 cocos2d-x 打算实现多线程化,从而充分利用现代智能手机的多核处理器资源。而多线程化的一个最重大需求,应该就是 cocos2d-x 3.0 新的渲染架构了。
cocos2d-x 3.0 的渲染架构设计来自 Zynga(原始设计文档)。在这个架构设计中,游戏将分为两个主要的线程:main thread 和 draw thread。
main thread 运行游戏的逻辑代码,例如在场景的 update() 函数中改变角色位置、在 schedule selector 函数中创建新敌人等等。
main thread 中的代码会改变 Scene 中的 Node Tree,但并不实际执行 OpenGL 渲染操作。作为替代,main thread 将绘图操作转换为一系列的 draw command,发送到一个 queue 中。
draw thread 则在每一帧绘制前,从 queue 里提取 draw command,然后进行优化,再转换为实际的 OpenGL 渲染操作。
在单线程架构中,游戏逻辑和渲染操作是顺序执行的。两者消耗的时间加起来如果超过 1/60 秒,就会导致游戏出现卡顿现象。
分成多线程后,main thread 有更多的时间来执行游戏逻辑。而 draw thread 也有更多的时间做优化和渲染操作,例如实现 automatic batching。
新渲染架构可能的实现方案
Zynga 的设计文档并没有给出细节实现原理,我就以大家讨论的内容整理了一个可能的方案。
main thread 会每 1/60 秒的频率调用 Scene::drawScene() 方法。这个方法有如下步骤:
发送一个 begin command 到一个 command queue
update() 执行所有的 schedule selector 和 event listener,游戏逻辑大部分就在其中。
扫描整个 Node Tree,构造一系列 draw command,并发送到 queue
发送一个 end command 到 queue,形成一个完整的 commands list
如果某次 drawScene() 消耗的时间超过 1/60 秒,则无需等待,立即开始下一次 drawScene() 调用。这样确保游戏的逻辑能够以最高 60fps 运行,但又不会超过 60fps。
draw thread 按照如下步骤以最快 1/60 秒的频率执行:
从 queue 中找出最后一组 list(如果 draw thread 每次绘图消耗时间超过 1/60 秒,queue 中就可能包含多组 list,放弃之前的 list 可以确保画面符合最近的游戏状态)。
对 list 进行处理,执行画面渲染。
更新 queue,删除已绘制的 list 及其之前的所有 list。
Texture 对象不再保存材质的实际数据,改为保存一个 texture id。材质的实际数据由 draw thread 进行管理。
当加载一个材质文件时,main thread 中构造一个新的 Texture 对象,并获得一个新的 texture id。然后发送 load texture command,并将材质文件名和 texture id 作为 command 的参数传递给 draw thread。draw thread 可以在自己线程里载入材质数据,或新开线程载入数据。
当一个 Texture 被 Sprite 使用时,增加 Texture 对象的引用计数。而使用该 Texture 的 Sprite 删除时,则减小引用计数。如果 TextureCache 需要释放不再引用计数为 0 的 Texture 对象,就发送 release texture command。
在 cocos2d-x 2.x 版中,CCNode 包含了位置、大小、缩放、旋转等信息,以及 OpenGL 绘图需要的数据。
在新架构里,main thread 维护的 Node Tree 中,Node 不再包含 OpenGL 绘图需要的数据。而是在构造 Node、改变 Node 状态时,才将 Node 的状态数据发送给 draw thread。
draw thread 根据 node id 维护 node 的绘图数据。
如果 main thread 执行后,Node 的状态没有发生改变,就无需发送该 Node 的 draw command,也就避免了 draw thread 对没有发生变化的 Node 重新计算绘图数据。
cocos2d-x 3.0 必须要实现线程安全?
在整个设计里,main thread 不停的往 command queue 追加数据,而 draw thread 每完成一次画面渲染就清理一次 command queue。因此 queue 的实现必须是线程安全的,主要的操作都应该满足原子性要求。
但从这个设计看,只要保证两点,那么 main thread 里的对象是不是线程安全根本无所谓:
queue 是线程安全的
draw thread 不使用自动释放的引用计数对象
所以这样一番分析后,感觉 cocos2d-x 3.0 要多线程化,就不一定要做大改动。
但实际上 cocos2d-x 3.0 里还需要多线程完成异步 IO(例如网络)、异步计算等等。而且提供一个可靠的线程安全架构,也方便开发者创建线程来优化游戏体验(比如把 AI 算法放入单独的线程)。
所以从整体架构上看,cocos2d-x 3.0 实现线程安全还是很有必要的。但实现线程安全是否就一定要用智能指针吗?这个我会另外写一篇文章来探讨此问题。
最后,欢迎大家多多提意见。
- END -