一、Cocos2d-X中的内存问题
游戏中的内存优化一直是个让人头疼的问题,特别是对于C++程序员来说,指针和内存需要主动去创建及释放.
特别是对内存概念不清楚的技术人员,经常会遇到指针丢失导致突然崩溃,内存越来大但是不知道何处占用.解决起来也是一头雾水,本篇文章粗略讲解一下cocos2d-X中的内存管理及优化
1. 分析工具
在windows开发中可以使用vs自带的诊断工具进行内存监测及分析.程序启动后,打开诊断工具的堆分析,可以通过截取快照来记录某个时间点的内存信息
![image.png](https://img-blog.csdnimg.cn/img_convert/1d57a0d658c85a64e9334f9e41a11c7b.png#clientId=u60398639-9444-4&from=paste&height=405&id=uec42f753&margin=[object Object]&name=image.png&originHeight=494&originWidth=647&originalType=binary&ratio=1&size=43691&status=done&style=none&taskId=ubbb7b52a-dd97-41d1-9b2e-cea65b2599d&width=531)
2. 内存管理分类
在游戏开发中需要重点管理的内存主要分为两类:
- 使用new或者malloc创建的指针
- 加载到内存中的纹理
二、指针管理
1. 主动创建的指针
主动创建的指针是指程序开发中使用的结构体,类,数组等,创建于堆内存中的指针,这些指针的创建和释放必须成对出现,例如: 在A类中new 了一个B ,那么B的释放必须要有,常用方式在析构中写delete B
2. 派生自cocos的指针
在cocos中,派生自Ref的类型拥有自动释放的功能,所有Ref对象通过 _referenceCount 来控制对象的释放.
当_referenceCount 为0时,该对象自动释放掉.
代码原型为:
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
// if(!poolManager->isObjectInPools(this)){
// retainlist.remove(this);
// }
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.
// This happens when 'autorelease/release' were not used in pairs with 'new/retain'.
//
// Wrong usage (1):
//
// auto obj = Node::create(); // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.
// obj->autorelease(); // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.
//
// Wrong usage (2):
//
// auto obj = Node::create();
// obj->release(); // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.
//
// Correct usage (1):
//
// auto obj = Node::create();
// |- new Node(); // `new` is the pair of the `autorelease` of next line
// |- autorelease(); // The pair of `new Node`.
//
// obj->retain();
// obj->autorelease(); // This `autorelease` is the pair of `retain` of previous line.
//
// Correct usage (2):
//
// auto obj = Node::create();
// obj->retain();
// obj->release(); // This `release` is the pair of `retain` of previous line.
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
#if CC_ENABLE_SCRIPT_BINDING
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeObjectProxy(this);
}
#endif // CC_ENABLE_SCRIPT_BINDING
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
其中 Ref :: retain() 函数可增加该对象的 _referenceCount ,通过 Ref :: release() 函数可以减少 该对象的 _referenceCount.
addchild(obj) 操作会对obj 进行 _referenceCount +1操作, obj->removeFromParent和 removeChild(obj) 会对会对obj 进行 _referenceCount -1操作,
关于 Ref :: autorelease() ,调用该方法后, 该对象会放入 AutoreleasePool 中,在下一帧 会将AutoreleasePool 中的所有对象的 _referenceCount - 1
三、 关于贴图的内存管理
贴图是游戏运行中占用内存的主要因素,通过优化贴图可以起到立竿见影的内存优化.
1. 贴图的优化
纹理优化的目的是让它们占用的内存尽量的小,那么纹理加载进内存后,大小计算公式如下:
纹理内存大小(字节) = 纹理宽度 x 纹理高度 x 像素字节
像素字节 = 像素通道数(R/G/B/A) x 通道大小(1字节/半字节)
从上面公式可以看到,纹理加载进内存后的大小跟尺寸/像素通道数/通道大小都有关系,我们就从它们着手优化,还可以通过提高复用率和合成图集达到优化的目的
游戏中的ui图片通常是比较零碎的元素, 通过UI制作工具将零碎的贴图进行组合.来实现美术素材的复用及不同设备的适配.
零碎的贴图会增加CPU的draw call,极大的影响游戏性能.通常的做法是将图片合并为图集(plist).虽然将贴图元素生成为图集会降低 IO 和 Draw Call ,但不是所有图片都适合制作为图集,我们通常会做一些限制:
-
图集的最大尺寸不要超过2048x2048
-
图集的宽高应相等,并且是2的n次方
-
宽或高超过512像素的贴图不放入图集中
ui的元素尽量拆分得足够细,以保证图片的复用性. 比如背景框可以做成64 x 64 的九宫格图,背景框中的细节单独切图.
2. 贴图的加载方式
通用的元素合并在一个图集中,可以在游戏开始的时候进行加载,单个模块使用到的贴图可以在模块初始化的时候加载,用完之后可以释放掉.
在cocos中图片的加载方式通常有两种:
- 单图的加载
Director::getInstance()->getTextureCache()->addImage(texturePath)
- 图集加载
SpriteFrameCache加载. 通过将Texture2D放入SpriteFrame中,再将SpriteFrame对象放入SpriteFrameCache
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("res/xxx.plist");
3. 贴图的释放
因为Ref的派生对象的释放需要_referenceCount 为0时才会执行. 因此Texture2D对象的释放需要满足以下条件
- 使用到该贴图的Node需要移除并释放掉
- 移除SpriteFrameCache中的引用
SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("res/xxx.plist");
- 删除TextureCache中的引用
Director::getInstance()->getTextureCache()->removeTextureForKey("res/xxx.png")