I am making a game and I noticed that during some scenes, my FPS kept dropping around the 55-60FPS area (using texture atlas). This drove me nuts so I decided to put all my assets to the Images.xcassets folder and voila, steady 60FPS.
我正在制作游戏,我注意到在某些场景中,我的FPS在55-60FPS区域(使用纹理图集)不断下降。这让我疯了,所以我决定把我所有的资产都放到Images.xcassets文件夹中,然后稳定60FPS。
I thought this was a fluke or that I was doing something wrong, so I decided to start a new project and perform some benchmarks...
我认为这是一个侥幸或我做错了什么,所以我决定开始一个新的项目,并执行一些基准......
Apple's documentation says that using texture atlas's will improve app performance. Basically, allowing your app to take advantage of batch rendering. However...
Apple的文档称使用纹理图集会提高应用程序性能。基本上,允许您的应用程序利用批量渲染。然而...
The Test (https://github.com/JRam13/JSGlitch):
测试(https://github.com/JRam13/JSGlitch):
- (void)runTest
{
SKAction *spawn = [SKAction runBlock:^{
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
sprite.position = CGPointMake(0, [self randomNumberBetweenMin:0 andMax:768]);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
[sprite runAction:move];
//notice I don't remove from parent (see test2 below)
[self addChild:sprite];
}
}];
SKAction *wait = [SKAction waitForDuration:.1];
SKAction *sequence = [SKAction sequence:@[spawn,wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];
}
Results:
结果:
Tests repeatedly show that using the xcassets performed way better than the atlas counterpart in FPS. The atlas does seem to manage memory marginally better than the xcassets though.
测试反复表明,使用xcassets的方式比FPS中的atlas对应方式更好。尽管如此,地图集确实比mcassets更好地管理内存。
Anybody know why these results show that images.xcassets has better performance than the atlas?
有谁知道为什么这些结果表明images.xcassets的性能比地图集好?
Some hypotheses I've come up with:
我想出了一些假设:
- xcassets is just better optimized than atlasas.
- xcassets比atlasas更好地优化。
- atlasas are good at drawing lots of images in one draw pass, but have bigger overhead with repeated sprites. If true, this means that if your sprite appears multiple times on screen (which was the case in my original game), it is better to remove it from the atlas.
- atlasas擅长在一次平局中绘制大量图像,但是重复精灵会产生更大的开销。如果为true,这意味着如果您的精灵在屏幕上出现多次(在我的原始游戏中就是这种情况),最好将其从地图集中删除。
-
atlas sheets must be filled in order to optimize performance - 必须填写atlas表以优化性能
Update
更新
For this next test I went ahead and removed the sprite from parent after it goes offscreen. I also used 7 different images. We should see a huge performance gain using atlas due to the draw count but...
对于下一个测试,我继续前进,并在它离开屏幕后从父级中删除了精灵。我还使用了7种不同的图像。我们应该看到由于抽奖计数而使用地图集的巨大性能提升但......
1 个解决方案
#1
14
First, revise your test to match this :
首先,修改您的测试以匹配此:
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
float spawnY = arc4random() % 768;
sprite.position = CGPointMake(0, spawnY);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
// next three lines replace the runAction line for move
SKAction *remove = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:@[move, remove]];
[sprite runAction:sequence];
[self addChild:sprite];
}
Rerun your tests and you should notice that your framerate NEVER deteriorates as in your tests. Your tests were basically illustrating what happens when you never remove nodes, but keep creating new ones.
重新运行测试,您应该注意到您的帧率从未像测试中那样恶化。您的测试基本上说明了当您从未删除节点时会发生什么,但不断创建新节点。
Next, add the following line to your ViewController when you set up your skview :
接下来,在设置skview时将以下行添加到ViewController:
skView.showsDrawCount = YES;
This will allow you to see the draw count and properly understand where you are getting your performance boost with SKTextureAtlas.
这样您就可以通过SKTextureAtlas查看抽奖计数并正确理解您在哪里获得性能提升。
Now, instead of having just one image , gather 3 images and modify your test by choosing a random one of those images each time it creates a node, you can do it something like this :
现在,不是只有一个图像,而是通过在每次创建节点时选择一个随机的图像来收集3个图像并修改测试,您可以这样做:
NSArray *imageNames = @[@"image-0", @"image-1", @"image-2"];
NSString *imageName = imageNames[arc4random() % imageNames.count];
In your code, create your sprite with that imageName each time through the loop. ie :
在您的代码中,每次循环时都使用该imageName创建您的精灵。即:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];
In your SKTextureAtlas test, use that same imageName obviously to create each sprite.
在您的SKTextureAtlas测试中,显然使用相同的imageName来创建每个精灵。
Now... rerun your tests and take note of the draw count in each test.
现在......重新运行测试并记下每次测试中的抽奖计数。
This should give you a tangible example of what batch rendering is about with SKTextureAtlas.
这应该为您提供有关SKTextureAtlas批处理渲染的实际示例。
It's no about rendering the same sprite image thousands of times.
这不是关于渲染相同的精灵图像数千次。
It's about drawing many different sprites images in the same draw pass.
它是关于在同一个绘图过程中绘制许多不同的精灵图像。
There is likely some overhead in getting this rendering optimization, but I think the draw count should be self explanatory as to why that overhead is moot when all things are considered.
获得此渲染优化可能会有一些开销,但我认为绘制计数应该是自我解释为什么在考虑所有事情时这种开销没有实际意义。
Now, you can hypothesize some more :)
现在,你可以假设更多:)
UPDATE
UPDATE
As mentioned in the comments, my post was to expose why your test was not a good test for the benefits of SKTextureAtlas and was flawed if looking to analyze it in a meaningful way. Your test was like testing for measles with a mumps test.
正如评论中所提到的,我的帖子是揭示为什么你的测试不是SKTextureAtlas的好处的好测试,并且如果想要以有意义的方式分析它是有缺陷的。您的测试就像用腮腺炎测试测试麻疹一样。
Below is a github project that I put together to pinpoint where an SKTextureAtlas is appropriate and indeed superior to xcassets.
下面是一个github项目,我将它放在一起,以确定SKTextureAtlas的适用位置,并且确实优于xcassets。
图谱对比
Just run the project on your device and then tap to toggle between tests. You can tell when it's testing with SKTextureAtlas because the draw count will be 1 and the framerate will be 60fps.
只需在您的设备上运行该项目,然后点击即可在测试之间切换。您可以通过SKTextureAtlas测试它的时间,因为绘制计数为1,帧速率为60fps。
I isolated what will be optimized with a SKTextureAtlas. It's a 60 frame animation and 1600 nodes playing that animation. I offset their start frames so that they all aren't on the same frame at the same time. I also kept everything uniform for both tests, so that it's a direct comparison.
我分离了将使用SKTextureAtlas进行优化的内容。它是一个60帧动画和1600个节点播放该动画。我偏移它们的起始帧,以便它们不是同时在同一帧上。我还为两个测试保持一致,所以这是一个直接的比较。
It's not accurate for someone to think that using SKTextureAtlas will just optimize all rendering. It's optimization comes by reducing draw count via batch rendering. So, if your framerate slowdown is not something that can be improved via batch rendering, SKTexture atlas is the wrong tool for the job. right ?
有人认为使用SKTextureAtlas只会优化所有渲染是不准确的。它的优化来自于通过批量渲染减少抽取计数。因此,如果您的帧速率减慢不是通过批量渲染可以改进的东西,那么SKTexture地图册就是错误的工具。对 ?
Similar to pooling of objects, where you can gain optimization via not creating and killing your game objects constantly, but instead reusing them from a pool of objects. However if you are not constantly creating and killing objects in your game, pooling ain't gonna optimize your game.
类似于对象池,您可以通过不经常创建和终止游戏对象来获得优化,而是从对象池中重用它们。但是,如果你不是经常在你的游戏中创建和杀死对象,那么合并就不会优化你的游戏。
Based on what I saw you describe as your game's issue in the discussion log , pooling is probably the right tool for the job in your case.
基于我在讨论日志中描述的游戏问题,池化可能是您工作中正确的工具。
#1
14
First, revise your test to match this :
首先,修改您的测试以匹配此:
for (int i=0; i<10; i++) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
sprite.xScale = 0.5;
sprite.yScale = 0.5;
float spawnY = arc4random() % 768;
sprite.position = CGPointMake(0, spawnY);
SKAction *action = [SKAction rotateByAngle:M_PI duration:1];
[sprite runAction:[SKAction repeatActionForever:action]];
SKAction *move = [SKAction moveByX:1200 y:0 duration:2];
// next three lines replace the runAction line for move
SKAction *remove = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:@[move, remove]];
[sprite runAction:sequence];
[self addChild:sprite];
}
Rerun your tests and you should notice that your framerate NEVER deteriorates as in your tests. Your tests were basically illustrating what happens when you never remove nodes, but keep creating new ones.
重新运行测试,您应该注意到您的帧率从未像测试中那样恶化。您的测试基本上说明了当您从未删除节点时会发生什么,但不断创建新节点。
Next, add the following line to your ViewController when you set up your skview :
接下来,在设置skview时将以下行添加到ViewController:
skView.showsDrawCount = YES;
This will allow you to see the draw count and properly understand where you are getting your performance boost with SKTextureAtlas.
这样您就可以通过SKTextureAtlas查看抽奖计数并正确理解您在哪里获得性能提升。
Now, instead of having just one image , gather 3 images and modify your test by choosing a random one of those images each time it creates a node, you can do it something like this :
现在,不是只有一个图像,而是通过在每次创建节点时选择一个随机的图像来收集3个图像并修改测试,您可以这样做:
NSArray *imageNames = @[@"image-0", @"image-1", @"image-2"];
NSString *imageName = imageNames[arc4random() % imageNames.count];
In your code, create your sprite with that imageName each time through the loop. ie :
在您的代码中,每次循环时都使用该imageName创建您的精灵。即:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];
In your SKTextureAtlas test, use that same imageName obviously to create each sprite.
在您的SKTextureAtlas测试中,显然使用相同的imageName来创建每个精灵。
Now... rerun your tests and take note of the draw count in each test.
现在......重新运行测试并记下每次测试中的抽奖计数。
This should give you a tangible example of what batch rendering is about with SKTextureAtlas.
这应该为您提供有关SKTextureAtlas批处理渲染的实际示例。
It's no about rendering the same sprite image thousands of times.
这不是关于渲染相同的精灵图像数千次。
It's about drawing many different sprites images in the same draw pass.
它是关于在同一个绘图过程中绘制许多不同的精灵图像。
There is likely some overhead in getting this rendering optimization, but I think the draw count should be self explanatory as to why that overhead is moot when all things are considered.
获得此渲染优化可能会有一些开销,但我认为绘制计数应该是自我解释为什么在考虑所有事情时这种开销没有实际意义。
Now, you can hypothesize some more :)
现在,你可以假设更多:)
UPDATE
UPDATE
As mentioned in the comments, my post was to expose why your test was not a good test for the benefits of SKTextureAtlas and was flawed if looking to analyze it in a meaningful way. Your test was like testing for measles with a mumps test.
正如评论中所提到的,我的帖子是揭示为什么你的测试不是SKTextureAtlas的好处的好测试,并且如果想要以有意义的方式分析它是有缺陷的。您的测试就像用腮腺炎测试测试麻疹一样。
Below is a github project that I put together to pinpoint where an SKTextureAtlas is appropriate and indeed superior to xcassets.
下面是一个github项目,我将它放在一起,以确定SKTextureAtlas的适用位置,并且确实优于xcassets。
图谱对比
Just run the project on your device and then tap to toggle between tests. You can tell when it's testing with SKTextureAtlas because the draw count will be 1 and the framerate will be 60fps.
只需在您的设备上运行该项目,然后点击即可在测试之间切换。您可以通过SKTextureAtlas测试它的时间,因为绘制计数为1,帧速率为60fps。
I isolated what will be optimized with a SKTextureAtlas. It's a 60 frame animation and 1600 nodes playing that animation. I offset their start frames so that they all aren't on the same frame at the same time. I also kept everything uniform for both tests, so that it's a direct comparison.
我分离了将使用SKTextureAtlas进行优化的内容。它是一个60帧动画和1600个节点播放该动画。我偏移它们的起始帧,以便它们不是同时在同一帧上。我还为两个测试保持一致,所以这是一个直接的比较。
It's not accurate for someone to think that using SKTextureAtlas will just optimize all rendering. It's optimization comes by reducing draw count via batch rendering. So, if your framerate slowdown is not something that can be improved via batch rendering, SKTexture atlas is the wrong tool for the job. right ?
有人认为使用SKTextureAtlas只会优化所有渲染是不准确的。它的优化来自于通过批量渲染减少抽取计数。因此,如果您的帧速率减慢不是通过批量渲染可以改进的东西,那么SKTexture地图册就是错误的工具。对 ?
Similar to pooling of objects, where you can gain optimization via not creating and killing your game objects constantly, but instead reusing them from a pool of objects. However if you are not constantly creating and killing objects in your game, pooling ain't gonna optimize your game.
类似于对象池,您可以通过不经常创建和终止游戏对象来获得优化,而是从对象池中重用它们。但是,如果你不是经常在你的游戏中创建和杀死对象,那么合并就不会优化你的游戏。
Based on what I saw you describe as your game's issue in the discussion log , pooling is probably the right tool for the job in your case.
基于我在讨论日志中描述的游戏问题,池化可能是您工作中正确的工具。