Kar*_*uil 9 sprite-kit sktextureatlas sktexture
在StackOverflow上经常提到SpriteKit自己进行内部缓存和重用.
如果我不再努力重用纹理或地图集,我可以从SpriteKit中获得什么样的缓存和重用行为?
Kar*_*uil 18
"长话短说:依靠Sprite Kit为你做正确的事." - @ LearnCocos2D
这是长篇故事,从iOS 9开始.
纹理庞大的图像数据不会直接保留在SKTexture
对象上.(参见SKTexture课程参考.)
SKTexture在必要时推迟加载数据.即使从大图像文件创建纹理也很快并且消耗很少的内存.
通常在创建相应的精灵节点时从磁盘加载纹理的数据.(或者实际上,只要需要数据,例如size调用方法时).
准备纹理的数据以在(第一)渲染过程中进行渲染.
SpriteKit对纹理庞大的图像数据有一些内置的缓存.两个功能:
纹理笨重的图像数据由SpriteKit缓存,直到SpriteKit感觉就像摆脱它一样.
根据SKTexture类引用:"一旦SKTexture对象准备好渲染,它就会保持准备状态,直到删除对纹理对象的所有强引用."
在当前的iOS中,它往往会更长时间,甚至在整个场景消失之后. StackOverflow评论引用Apple技术支持:"iOS发布由+ textureWithImageNamed缓存的内存:或+ imageNamed:当它看起来合适时,例如当它检测到内存不足的情况时."
在模拟器中,运行一个测试项目,我能够看到一个后立即回收的纹理内存removeFromParent.然而,在物理设备上运行时,记忆似乎仍然存在; 重复渲染和释放纹理导致没有额外的磁盘访问.
我想知道:渲染内存是否可以在某些内存关键情况下(当纹理保留但当前未显示时)提前释放?
SpriteKit巧妙地重用缓存的庞大图像数据.
在我的实验中,很难不重复使用它.
假设您在精灵节点中显示了一个纹理,但不是重复使用该SKTexture对象,而是调用[SKTexture
textureWithImageNamed:]相同的图像名称.纹理不会与原始纹理指针相同,但它将共享庞大的图像数据.
无论图像文件是否是图集的一部分,上述情况都是正确的.
无论原始纹理是使用[SKTexture textureWithImageNamed:]还是使用加载,都是如此
[SKTextureAtlas
textureNamed:].
另一个例子:假设您使用创建纹理图集对象[SKTextureAtlas atlasNamed:].你使用它的一个纹理textureNamed:,你不保留地图集.您在精灵节点中显示纹理(因此纹理在您的应用程序中强烈保留),但您不必SKTexture在缓存中跟踪该特定内容.然后你再做一遍:新纹理图集,新纹理,新节点.所有这些对象都将被新分配,但它们相对轻量级.同时,重要的是:最初加载的庞大图像数据将在实例之间透明地共享.
试试这个:你按名称加载一个怪物图集,然后取一个orc纹理并在一个兽人精灵节点中渲染它.然后,玩家返回主屏幕.您在应用程序状态保留期间编码orc节点,然后在应用程序状态恢复期间对其进行解码.(当它编码时,它不会对其二进制数据进行编码;它会对其名称进行编码.)在恢复的应用程序中,您将创建另一个orc(具有新的图集,纹理和节点).这个新的兽人会用解码的orc分享它庞大的兽人数据吗?是.是的,orcking会.
使纹理不重用纹理图像数据的唯一方法是使用它来初始化它[SKTexture
textureWithImage:].当然,也许UIImage会对图像文件进行自己的内部缓存,但无论哪种方式,SKTexture
都负责数据,并且不会在其他地方重用渲染数据.
简而言之:如果你在游戏中同时出现了两个相同的精灵,那么他们可以有效地使用内存.
将这两点放在一起:SpriteKit有一个内置缓存,可以持久保存重要的庞大图像数据并巧妙地重用它.
换句话说,它只是有效.
没有承诺.在运行测试应用程序的模拟器中,我可以很容易地证明SpriteKit在我真正完成它之前正在从缓存中删除我的纹理数据.
但是,在原型设计过程中,即使您从未重复使用单个图集或纹理,您也可能会惊讶地发现应用程序的行为相当不错.
SpriteKit具有专门用于纹理图集的缓存机制.它的工作原理如下:
您调用[SKTextureAtlas atlasNamed:]加载纹理图集.(如前所述,这还没有加载庞大的图像数据.)
您可以在应用中的某个位置强烈保留地图集.
稍后,如果[SKTextureAtlas atlasNamed:]使用相同的地图集名称进行调用,则返回的对象将与指定的保留地图集相同.textureNamed:然后,使用地图集中提取的纹理
也将是指针相同的.(更新:在iOS10下,纹理不一定是指针相同的.)
应该提到的纹理对象不保留它们的地图集.
所以我看到你正构建自己的缓存和重用机制.你为什么这样做?
最终,您可以获得有关何时保留或清除某些纹理的更好信息.
您可能需要完全控制加载时序.举例来说,如果你希望你的纹理瞬间呈现先渲染时,你会使用preload的方法从SKTexture和
SKTextureAtlas.在这种情况下,您应该保留对预加载纹理或地图集的引用,对吧?或者,SpriteKit会不会为你缓存它们?不清楚.自定义图集或纹理缓存是保持完全控制的好方法.
在优化的某个阶段(天堂禁止过早!!),无论多么轻巧,都可以一遍又一遍地停止创建新的SKTexture和/或
SKTextureAtlas对象.可能你首先要构建地图集重用机制,因为地图集的重量较轻(毕竟它们有一个纹理字典).稍后您可以构建一个单独的纹理缓存机制来重用非atlas SKTexture对象.或者也许你永远不会到达那第二个.毕竟,你很忙,厨房里没有自己打扫卫生,该死的.
总而言之,您的缓存和重用行为可能最终会与SpriteKit类似.
SpriteKit的纹理缓存如何影响您自己的纹理缓存设计?以下是要记住的事情(从上面):
使用命名纹理时,无法直接控制释放大量图像数据的时间.您释放您的引用,SpriteKit会在需要时释放内存.
您可以使用这些preload方法控制加载庞大图像数据的时间.
如果您依赖SpriteKit的内部缓存,那么您的atlas缓存只需要保留对SKTextureAtlas对象的引用,而不是返回它们.atlas对象将在整个应用程序中自动重用.
同样,您的纹理缓存只需要保留对SKTexture对象的引用,而不是返回它们.庞大的图像数据将在整个应用程序中自动重用.(不过,这一点让我感到很奇怪;验证良好行为是一种痛苦.)
鉴于最后两点,请考虑单例缓存对象的设计替代方案.相反,您可以在精灵对象或其控制器上保留使用中的地图集.因此,对于控制器的生命周期,应用程序中的任何调用atlasNamed:都将重用指针相同的图集.
两个指针相同的SKTexture对象共享相同的内存,是的,但由于SpriteKit缓存,反过来不一定是真的.如果您正在调试内存问题并找到两个SKTexture
您希望指针相同但却没有的对象,那么它们仍然可能正在共享其庞大的图像数据.
我是一个工具新手,所以我只使用Allocations工具测量了发布版本中的整体应用内存使用情况.
我发现"All Heap&Anonymous VM"会在顺序运行中在两个稳定值之间交替.我运行了几次测试并使用最低内存值作为结果.
对于我的测试,我有两个不同的地图集,每个地图有两个图像; 调用地图集A和B以及图像1和2.源图像较大(一个760 KiB,一个950 KiB).
使用地图集加载地图集[SKTextureAtlas atlasNamed:].使用加载纹理[SKTexture textureWithImageNamed:].在下表中,load实际上意味着"放入精灵节点并渲染".
All Heap
& Anon VM
(MiB) Test
--------- ------------------------------------------------------
106.67 baseline
106.67 preload atlases but no nodes
110.81 load A1
110.81 load A1 and reuse in different two sprite nodes
110.81 load A1 with retained atlas
110.81 load A1,A1
110.81 load A1,A1 with retained atlas
110.81 load A1,A2
110.81 load A1,A2 with retained atlas
110.81 load A1 two different ways*
110.81 load A1 two different ways* with retained atlas
110.81 load A1 or A2 randomly on each tap
110.81 load A1 or A2 randomly on each tap with retained atlas
114.87 load A1,B1
114.87 load A1,A2,B1,B2
114.87 load A1,A2,B1,B2 with preload atlases
* Load A1 two different ways: Once using [SKTexture
textureWithImageNamed:] and once using [SKTextureAtlas
textureNamed:].
Run Code Online (Sandbox Code Playgroud)
在调查时,我发现了一些关于SpriteKit中纹理和atlas对象内部结构的真实事实.
有趣?这取决于你感兴趣的东西!
SKTextureAtlas当加载地图集时[SKTextureAtlas atlasNamed:],在运行时检查其纹理会显示一些重用.
在Xcode构建期间,脚本将来自各个图像文件的图集编译成许多大的精灵图像(受大小限制,并按@ 1x @ 2x @ 3x分辨率分组).图集中的每个纹理都是通过束路径引用其精灵图像,存储在
_imgName(_isPath设置为true)中.
图集中的每个纹理都由其单独标识
_subTextureName,并且textureRect嵌入其精灵表中.
共享相同精灵图像的地图册中的所有纹理都将具有相同的非零ivars _originalTexture和
_textureCache.
共享_originalTexture本身就是一个SKTexture对象,可能代表了整个精灵表单图像.它没有
_subTextureName自己的,它textureRect是(0, 0, 1,
1).
如果地图集从内存中释放然后重新加载,则新副本将具有不同的SKTexture对象,不同的_originalTexture
对象和不同的_textureCache对象.从我所看到的,只有_imgName(即实际的图像文件)将新地图集连接到旧地图集.
SKTextureAtlas当使用加载纹理时[SKTexture textureWithImageNamed:],它可能来自地图集,但它似乎不是来自
SKTextureAtlas.
以这种方式加载的纹理与上述不同:
它有一个简短的_imgName,如"giraffe.png",并_isPath设置为false.
它有一个未设置的_originalTexture.
它(显然)是它自己的_textureCache.
SKTexture加载的两个对象textureWithImageNamed:(具有相同的图像名称)没有任何共同点,除了_imgName.
然而,如上所述,这种纹理配置与其他种类的纹理配置共享庞大的图像数据.这意味着缓存是在实际图像文件附近完成的.
| 归档时间: |
|
| 查看次数: |
1121 次 |
| 最近记录: |