Cocos2dx引擎笔记——内存优化

Cocos2dx引擎笔记——内存优化,第1张

概述内存优化原理 纹理最耗应用内存, 纹理几乎会占据90%应用内存。所以尽量最小化应用的纹理内存使用,否则应用很有可能会因为低内存而崩溃。 认识瓶颈寻找方案 什么样的纹理最耗应用内存?消耗多少内存?利用苹果的工具“Allocation & Leaks”。你可以在Xcode中长按“Run”命令,选择“ Profile ”来启动这两个工具。如下所示: 使用Allocation工具可以监控应用的内存使用,使 内存优化原理

纹理最耗应用内存, 纹理几乎会占据90%应用内存。所以尽量最小化应用的纹理内存使用,否则应用很有可能会因为低内存而崩溃。

认识瓶颈寻找方案

什么样的纹理最耗应用内存?消耗多少内存?利用苹果的工具“Allocation & Leaks”。你可以在Xcode中长按“Run”命令,选择“ Profile ”来启动这两个工具。如下所示:

使用Allocation工具可以监控应用的内存使用,使用Leaks工具可以观察内存的泄漏情况。 此外还可用一些代码获取游戏内存使用的其他信息,如下所示:

CCTextureCache::sharedTextureCache()->dumpCachedTextureInfo();

调用这个代码后,游戏便会在DEBUG模式运行,这时你会在Xcode控制台窗口看到一些格式工整的日志信息。

Cocos2d: cocos2d: "cc_fps_images" rc=5 ID=3 256 x 32 @ 16 bpp => 16 KBCocos2d: cocos2d: "XXX/hd/actor.pvr.ccz" rc=1059 ID=4 2048 x 2048 @ 32 bpp => 16384 KBCocos2d: cocos2d: CCTextureCache dumpDeBUGInfo: 2 textures,for 16400 KB (16.02 MB)

从上可以看到会显示纹理的名称、引用计数、ID、大小及每像素的位数。最重要的是会显示内存的使用情况。如“cc_fps_images”指消耗了16KB内存,而“actor.pvr.ccz”消耗了16M内存。

切勿过度优化

根据实际需求进行优化,权衡图像质量图像内存使用。千万不要过度优化!

Ccos2d-x内存优化分为三个等级 一、客户端等级,最重要的的优化等级。因为我们要在Cocos2d-x引擎顶层编译游戏,引擎自身会提供一些优化选项。 在这个等级我们可以进行大部分优化。简而言之,我们可以优化纹理、音频、字体及粒子的内存使用。 1、纹理优化,什么因素对纹理内存使用的影响最大? # 纹理格式(压缩还是非压缩)、颜色深度和大小。我们可以使用PVR格式纹理减少内存使用。推荐纹理格式为pvr.ccz。纹理使用的每种颜色位数越多,图像质量越好,但是越耗内存。所以我们可以使用颜色深度为RGB4444的纹理代替RGB8888,这样内存消耗会降低一半。此外超大的纹理也会导致内存相关问题。所以最好使用中等大小的纹理。 2、音频优化 # 音频文件数据格式、比特率及采样率。推荐使用MP3数据格式的音频文件,因为AndroID平台和iOS平台均支持MP3格式,此外MP3格式经过压缩和硬件加速。背景音乐文件大小应该低于800KB,最简单的方法就是减少背景音乐时间然后重复播放。音频文件采样率大约在96-128kbps为佳,比特率44kHz就够了。 3、字体和粒子优化?在此有两条小提示:使用BMFont字体显示游戏分数时,请尽可能使用最少数量的文字。例如只想要显示单位数的数字,你可以移除所有字母。至于粒子,可以通过减少粒子数来降低内存使用。 二、引擎等级

需要OpenGLES高手,普通人可以略过。

三、C++语言等级

遵循Cocos2d-x内置的内存管理原则,尽量避免内存泄露。

提示和技巧 一帧一帧载入游戏资源 减少绘制调用,使用“CCSpriteBatchNode” 载入纹理时按照从大到小的顺序 避免高峰内存使用 使用载入屏幕预载入游戏资源 需要时释放空闲资源 收到内存警告后释放缓存资源. 使用纹理打包器优化纹理大小、格式、颜色深度等 使用JPG格式要谨慎! 请使用RGB4444颜色深度16位纹理 请使用NPOT纹理,不要使用POT纹理 避免载入超大纹理 推荐1024*1024 NPOT pvr.ccz纹理集,而不要采用RAW PNG纹理 推荐阅读

Steffen Itterheim's cocos2d memory optimization tutorialsApple's developer guide for reducing memory usage

@H_301_155@引用计数(Reference Count)

引用计数是c/c++项目中一种古老的内存管理方式。参考苹果官方文档NSautoreleasePool Class Reference。

@H_301_155@ @H_301_155@自动释放池(autoReleasePool)

CCautoreleasePool和cocoa的NSautoreleasePool有相同的概念和API,但是有两点比较重要的不同:

CCautoreleasePool不能被开发者自己创建。Cocos2d-x会为每一个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,仅仅需要专注于release/retain cocos2d::CCObject的对象

CCautoreleasePool不能被用在多线程中,所以假如你游戏需要网络线程,请仅仅在网络线程中接收数据,改变状态标志,不要这个线程里面调用cocos2d接口。下面就是原因:

CCautoreleasePool的逻辑是,当你调用object->autorelease(),object就被放到自动释放池中。自动释放池能够帮助你保持这个object的生命周期,直到当前消息循环的结束。在这个消息循环的最后,假如这个object没有被其他类或容器retain过,那么它将自动释放掉。

例如,layer->addChild(sprite),这个sprite增加到这个layer的子节点列表中,他的声明周期就会持续到这个layer释放的时候,而不会在当前消息循环的最后被释放掉。

这就是为什么你不能在网络线层中管理CCObject生命周期,因为在每一个UI线程的最后 ,自动释放对象将会被删除,所以当你调用这些被删掉的对象的时候,你就会遇到crash。

CCObject::release(),retain() and autorelease()

简而言之,这只有两种情况你需要调用release()方法

你new一个cocos2d::CCObject子类的对象,例如CCSprite,cclayer等。

你得到cocos2d::CCObject子类对象的指针,然后在你的代码中调用过retain方法。

下面例子就是不需要调用retain和release方法:

@H_403_218@CCSprite* sprite = CCSprite::create("player.png");

这里就没有更多的代码用于sprite了。但是请注意sripte->autorelease()已经在CCSprite::create(const char*)方法中被调用了,因此这个sprite将在消息循环的最后自动释放掉。

使用静态构造函数

Cocos2d-x中所有的类,除了单例,都提供了静态构造函数,这些静态构造函数包含4项 *** 作:

新建一个对象

调用object->init(…)

假如初始化成功,例如,成功的找到纹理文件,那么接下来将会调用object->autorelease()。

返回这个已经被标记了autorelease的对象。

所有CCAsdf::createWithXxxx(…)这种类型的函数都有以上这些方式。使用这些静态构造函数,你不需要关心“new”, “delete”和“autorelease”,只需要关心object->retain() 和 object->release()。

一个错误的例子

一个开发者报告了一个使用CCArray 并导致crash的例子

@H_403_218@bool HelloWorld::init(){ bool bRet = false; do { ////////////////////////////////////////////////////////////////////////// // super init first ////////////////////////////////////////////////////////////////////////// CC_BREAK_IF(! cclayer::init()); ////////////////////////////////////////////////////////////////////////// // add your codes below... ////////////////////////////////////////////////////////////////////////// CCSprite* bomb1 = CCSprite::create("Closenormal.png"); CCSprite* bomb2 = CCSprite::create("Closenormal.png"); CCSprite* bomb3 = CCSprite::create("Closenormal.png"); CCSprite* bomb4 = CCSprite::create("Closenormal.png"); CCSprite* bomb5 = CCSprite::create("Closenormal.png"); CCSprite* bomb6 = CCSprite::create("Closenormal.png"); addChild(bomb1,1); addChild(bomb2,1); addChild(bomb3,1); addChild(bomb4,1); addChild(bomb5,1); addChild(bomb6,1); m_pBombsdisplayed = CCArray::create(bomb1,bomb2,bomb3,bomb4,bomb5,bomb6,NulL); //m_pBombsdisplayed 是在头文件中被定义为一个 protected 变量. // <--- 我们应该添加在这里m_pBombsdisplayed->retain()方法来防止在HelloWorld::refreshData()中crash。 this->scheduleUpdate(); bRet = true; } while (0); return bRet;}voID HelloWorld::update(ccTime dt){ refreshData();}voID HelloWorld::refreshData(){ m_pBombsdisplayed->objectAtIndex(0)->setposition(cpp(100,100));}

他的错误是m_pBombsdisplayed是使用CCArray::create(…)创建的,这种创建方式是静态构造方式,这个数组被标记了autorelease。

所以这个数组会在当前消息循环的最后被CCautoreleasePool释放掉。当后面的消息循环调用HelloWorld::update(ccTime)的时候,m_pBombsdisplayed已经是一个野指针了,这就将引起崩溃。为了修复这个崩溃情况,我们需要增加m_pBombsdisplayed->retain()在 m_pBombsdisplayed =CCArray::create(…);之后, 并且在 HelloWorld::~HelloWorld() 的析构函数中调用m_pBombsdisplayed->release()。

纹理缓存(Texture Cache) 纹理缓存是将纹理缓存起来方便之后的绘制工作。每一个缓存的图像的大小,颜色和区域范围都是可以被修改的。这些信息都是存储在内存中的,不用在每一次绘制的时候都发送给GPU。 CCTextureCache

Cocos2d通过调用CCTextureCache或者CCSpriteFrameCache来缓存精灵的纹理。

当这个精灵调用CCTextureCache 或 CCSpriteFrameCache的方法的时候,cocos2dx将使用纹理缓存来创建一个CCSprite。所以你可以预先将纹理加载到缓存中,这样你在场景中使用的时候就非常方便了。怎么样加载这些纹理就看你自己的想法?你可以选择异步加载方式,这样你就可以为loading场景增加一个进度条。

当你创建一个精灵,你一般会使用CCSprite::create(pszfilename)。假如你去看CCSprite::create(pszfilename)的实现方式,你将看到它将这个图片增加到纹理缓存中去了:

@H_403_218@bool CCSprite::initWithfile(const char *pszfilename){ CCAssert(pszfilename != NulL,"InvalID filename for sprite"); CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(pszfilename); if (pTexture) { CCRect rect = CCRectZero; rect.size = pTexture->getContentSize(); return initWithTexture(pTexture,rect); } // don't release here. // when load texture Failed,it's better to get a "transparent" sprite than a crashed program // this->release(); returnfalse;}

上面代码显示一个单例在控制加载纹理。一旦这个纹理被加载了,在下一时刻就会返回之前加载的纹理引用,并且减少加载的时候瞬间增加的内存。(详细API请看CCTextureCache API)

CCSpriteFrameCache

CCSpriteFrameCache单例是所有精灵帧的缓存。使用Spritesheet和与之相关的xml文件,我们可以加载很多的精灵帧到缓存中,那么之后我们就可以从这个缓存中创建精灵对象了。和这个xml相关的纹理集一般是一个很大的图片,里面包含了很多小的纹理。下面就是一个纹理集的例子:

加载纹理集到CCSpriteFrameCache的三种方式

加载一个xml(pList)文件 加载一个xml(pList)文件和一个纹理集 通过CCSpriteFrame和一个精灵帧的名字

具体完整API请看CCSpriteFrameCache API。

样例:

@H_403_218@CCSpriteFrameCache* cache = CCSpriteFrameCache::sharedSpriteFrameCache(); cache->addSpriteFramesWithfile(“family.pList”,“family.png”);

使用缓存的原因就是减少内存,因为当你使用一个图片创建一个精灵的时候,如果这个图片不在缓存中,那么就会将他加载到缓存中,当你需要用相同的图片来新建精灵的时候,就可以直接从缓存中取得,而不用再去新分配一份内存空间。

CCSpriteFrameCache vs. CCSpriteBatchNode 最好是尽可能的使用Spritesheets (CCSpriteBatchNodes)。这样的方式是减少draw的调用次数。Draw的调用是非常耗时的。每一个batchnode调用一次draw就可以绘制上面所有的节点,而不是每一个节点的draw都单独调用一次, CCSpriteBatchNode渲染所有的子节点只需要一次,只需要调用一次draw。那就是为什么你需要把精灵加载batch node的原因,因为可以统一一起渲染。但是只有这个精灵使用的纹理包含在batch node中的才可以添加到batch node上,因为batch node一次只渲染这相同的纹理集。 假如你把精灵添加到其他的节点上。那么每一个精灵就会调用自己的draw函数,batch node就没起作用了。 CCSpriteBatchNode也是一个常用节点。你可以从场景中像其他节点一样移除掉。纹理集和精灵帧都被缓存在CCTextureCache 和 CCSpriteFrameCache单例中。假如你想要从内存中移除纹理集和精灵帧,那么你不得不通过缓存类来完成这个工作。 各平台硬件所允许的最大纹理尺寸

纹理大小由于硬件和 *** 作系统原因是有限制的。这里我们提供一个不同平台模拟器上纹理大小限制的表格

iPhone3 iPhone3gs iPhone4
platform maxsize in pixels
win32 2048*2048
AndroID 4096*4096
1024*1024
2048*2048
2048*2048

在真实的机器上面,也有一些不同的限制,这里有一些测试结果:G31024*1024,iPhone4 2048*2048

因此对于开发者来说,假如你想要跨平台,并且游戏运行流畅,你最好保持你的纹理大小小于1024*1024,这个是大多数机器的限制。


声明:本文是对http://www.cocos.com/帮助文档,的阅读笔记

总结

以上是内存溢出为你收集整理的Cocos2dx引擎笔记——内存优化全部内容,希望文章能够帮你解决Cocos2dx引擎笔记——内存优化所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1070798.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存