Has someone of you ever noticed that scaling a sprite up to more than 100% can cause the render time per frame to increase and never go down again?
I've set up a test project based on a cocos2d 2.0 template (I also tested it with 2.1 and it also does happen). When touching the screen (testing with an iPad 3) it creates 100 sprites that are scaled by 1.5
When there are 5000 it removes them all. When they get removed the render time very often stays at 0.016. By double tapping the home button (causing some interrupt to happen) you can make it go back to 0.001 (when there are no sprites).
I did a lot of testing when and how to cause this and I came to the conclusion that it only happens when scaling something.
While in 0.016 "mode" there is a constant memory increase (look at the allocations tool) and as soon as you double tap the home button it goes down again slowly. These allocations come from the gyroscope / accelerometer code that I
put in there. When in 0.001 "mode" it works fine but as soon as the render time shows 0.016 memory gets allocated endlessly and it is never freed.
Especially with really big scalings this thing can be achieved easily. Does anyone have an idea how to fix this?
This does happen when touching the screen in my test project. You can download it here:
https://dl.dropboxusercontent.com/u/40859730/RenderTimeIssue.zip
static int spriteCount = 0;
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
for (int i = 0; i < 100; i++) {
CCSprite *icon = [CCSprite spriteWithFile: #"Icon.png"];
[icon setPosition: ccp(arc4random() % 1000, arc4random() % 1000)];
[self addChild: icon];
icon.tag = spriteCount;
// this line causes the "0.016 bug", comment it out and the frame rate does go back to 0.001 when 5000 sprites are reached
icon.scale = 1.5f; // while scaling up more than 1.0 causes the problem, scaling down does not
spriteCount++;
}
if (spriteCount >= 5000) {
for (int i = 0; i < spriteCount; i++) {
[self removeChildByTag: i cleanup: YES];
}
spriteCount = 0;
}
return YES;
}
EDIT: Here are some images that show the memory increase:
http://imgur.com/a/Jy70a
I've experienced this problem on cocos2d 2.0.
But when i've upgraded to 2.1, this dont happen anymore.
Try to upgrade cocos2d to 2.1 version. There's a lot of performance improvements on rendering images.
Related
I have a game built in SpriteKit that I occasionally work on. I started working on it when iOS 8 was still the latest version of iOS. Back then, it always ran at 60fps or almost (on a physical device). However, since iOS 9, and now on iOS 10, the game runs at a measly 12fps on my iPhone 5. It actually usually runs faster on the simulator (13 - 14fps), which is unheard of.
I did an analysis with Instruments and it appears that some of the slowness comes from the app doing two enumerations every frame. Here they are:
-(void)moveBackground
{
[self enumerateChildNodesWithName:#"stars" usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *bg = (SKSpriteNode *)node;
CGPoint bgVelocity = CGPointMake(0, -150); //The speed at which the background image will move
CGPoint amountToMove = CGPointMultiplyScalar (bgVelocity,0.08);
bg.position = CGPointAdd(bg.position,amountToMove);
if (bg.position.y <= 0)
{
bg.position = CGPointMake(bg.position.x, bg.position.y + bg.size.height/2);
}
}];
[self enumerateChildNodesWithName:#"opbg" usingBlock:^(SKNode *node, BOOL *stop)
{
SKSpriteNode *opbg = (SKSpriteNode *)node;
CGPoint bgVelocity = CGPointMake(0, -150); //The speed at which the background image will move
CGPoint amountToMove = CGPointMultiplyScalar (bgVelocity,0.08);
opbg.position = CGPointAdd(opbg.position,amountToMove);
if (opbg.position.y <= 0)
{
[self.opbg removeFromParent];
}
}];
}
These enumerations move the starting background until it is offscreen, then remove it from the parent, and cycle the regular background once that one is done. Is there a more efficient way of doing this? Additionally, is there another way of optimizing the app without losing any quality? My images are relatively large (background is 1080x3840), but I'm not sure if I can use smaller ones and then upscale them without losing quality.
Any help improving my framerate is appreciated, and I can show more code if needed.
Thanks
The biggest change between iOS 8 and 9 was the addition of a cameranode:
https://developer.apple.com/reference/spritekit/skcameranode
And an infamous amount of performance degradation.
There's a litany of Apple forum complaints about performance on iOS 9. There was no communication from Apple in a timely and satisfactory manner. Many SpriteKit users and potential users were lost as a result of the lack of meaningful fixes and explanations.
In your case, switching to use of the camera to do your parallaxes might regain some performance.
Here's some of the issues regarding performance being discussed, so you might see if anyone was doing something similar:
https://forums.developer.apple.com/thread/14487
https://forums.developer.apple.com/thread/30651
Working on a game for iOS, using the sprite-kit framework to build it. The game runs smoothly for about the first minute in a half (maintaining the 60 fps rate), but as the player progresses into the game, the frame rate slowly starts to decrease. With time it even drops as low as 8 fps. I thought this was a result of the added debris and obstacles in the game, so I made an effort to remove a lot of them from the parent after time. This is how the game is set up:
There are 6 NSMutableArrays for the different types of debris that fall in the game. This is the format for each of them:
-(void)spawnDebris6 {
SKSpriteNode * debris6 = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"debris6.png"] size:CGSizeMake(20, 20)];
debris6.zPosition = 1.0;
SKEmitterNode *debrisTrail = [SKEmitterNode kitty_emitterNamed:#"Dtrail"];
debrisTrail.zPosition = -1.0;
debrisTrail.targetNode = self;
[debris6 addChild:debrisTrail];
debris6.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:25];
debris6.physicsBody.allowsRotation = NO;
debris6.physicsBody.categoryBitMask = CollisionDebris;
//set up a random position for debris
//RandomPosition = arc4random() %248;
//RandomPosition = RandomPosition + 10;
debris6.position = CGPointMake (302, self.size.height + 65);
[_debris6 addObject:debris6];
[self addChild:debris6];
//next Spawn:
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:deb6Time],
[SKAction performSelector:#selector(spawnDebris6) onTarget:self],
]]];
if (_dead == YES) {
[self removeAllActions];
}
if (debris6.position.y > 568) {
[self removeFromParent];
}
}
Each of the NSMutableArrays appear over time - The first one appears at 0s, 2 # 10s, 3 # 40s, 4 #80s, etc. And when a new one appears I've added code to make former ones appear less frequently and also removed some (to lower the frame rate) yet I still notice a slower frame rate after about 90 seconds.
I don't see why this would be affecting the frame rate as I've minimised the particle birth rate, and life span, and made an effort to delete and slow down spawn rates of the debris over time. Why is the FPS rate slowly depreciating?
If more info is needed please let me know and I'll update this post.
I had a similar problem. It is probably the SKEmitterNodes. Continue to play around with them and lower birth rates and life spans, maybe even range etc. If the problem persists, see if you can take them out. It may not look as good, but sometimes you have to sacrifice looks for functionality. Also I'd recommend maybe having the debris appear at different times and heights, so you still have a full screen, but also a smooth running screen.
I've been developing a game in Cocos2D for about 3 years which utilizes a transparent background to show a UIView. The reason for this is to have the parallax background still run as Cocos2D does scene transitions.
I'm having a new issue that started when I updated to iOS 7. Slow down occurs in combination of these circumstances:
-ONLY if the parallax background's frame position has changed.
-If I destroy an enemy which emits small sprites and a particle effect.
So it's the combination of those two things and it only happens sometimes. The debug value of the frame rate does not dip when the slow down happens. If I load a new scene it goes back to normal. Sometimes when I destroy another enemy the slow down disappears as well.
I have code in my parallax UIView that runs just about every frame of in-gameplay. I summed down the issue to one line:
-(void)updateImagePosWithPos:(CGPoint)pos{ // in game
// create vel based on last currentPos minus new pos
CGPoint vel = CGPointMake(currentPos.x-pos.x, currentPos.y-pos.y);
// init variables tmpVel and tempTotalImages
CGPoint tmpVel = CGPointZero;
int tmpTotalImages = 0;
// create indexLayerArr
NSMutableArray *indexLayerArr = [NSMutableArray array];
// for every parallax layer, add the number of images horizontally minus 1 to indexLayerArr
for (int j=0; j<totalLayers; ++j){
[indexLayerArr addObject:[NSNumber numberWithInt:[[totalImagesArr objectAtIndex:j] intValue]-1]];
}
int i = 0;
for (UIImageView *imageView in self.subviews) {
CGRect tmpRect = CGRectZero;
NSMutableArray *tmpRectArr = [rectContainer objectAtIndex:imageView.tag];
float speed = 0.00;
tmpTotalImages = [[totalImagesArr objectAtIndex:imageView.tag] intValue];
speed = [[speedArr objectAtIndex:imageView.tag] floatValue];
tmpVel = CGPointMake(vel.x*speed, vel.y*speed);
i = [[indexLayerArr objectAtIndex:imageView.tag] intValue];
tmpRect = [[tmpRectArr objectAtIndex:i] CGRectValue];
if(tmpRect.origin.x - tmpVel.x > wins.width){
tmpRect.origin.x -= (tmpTotalImages)*tmpRect.size.width;
}
else if(tmpRect.origin.x - tmpVel.x < -tmpRect.size.width){
tmpRect.origin.x += (tmpTotalImages)*tmpRect.size.width;
}
tmpRect.origin.x -= tmpVel.x;
tmpRect.origin.y += tmpVel.y;
[tmpRectArr replaceObjectAtIndex:i withObject:[NSValue valueWithCGRect:tmpRect]];
imageView.frame = [[tmpRectArr objectAtIndex:i] CGRectValue]; // <-- slow down cause
i--;
[indexLayerArr replaceObjectAtIndex:imageView.tag withObject:[NSNumber numberWithInt:i]];
}
currentPos = CGPointMake(pos.x, pos.y);
}
See commented line imageView.frame = [[tmpRectArr objectAtIndex:i] CGRectValue];
So if I comment that line out, the problem will never happen. If I keep the line and as well as don't change the values of tempRect, the problem also won't happen.
It looks like there's an issue in iOS 7 in changing the UIImageView's frame position, but only sometimes. Just wondering what other alternatives could I use? Or am I doing something definitely wrong in iOS 7?
Not a solution to your problem but workarounds. I'll start with the one that's probably requires the most code changes.
You don't actually have to have a UIView in order to keep background with transitions. Instead if you implement the background entirely in cocos2d (as part of the scene), you can achieve the same effect if instead of replacing scenes you transition layers in and out. Scene transitions for the most part use the same actions that also work on nodes.
Implement the background using cocos2d nodes, and have one parent node acting as the container (ie "layer") of the background nodes. You can do one of two things with that node:
a. Edit CCDirectorIOS's code and add a reference to your background node. Update the node before all other nodes in the drawScene method, by calling visit on the background node just before [_runningScene visit].
b. When transitioning to a new scene, either remove the background node from the current scene and add it to the new scene, or create a copy of the background with all the same settings and add it to the new scene. Ensure the copy starts with the exact same state as the original. Though this won't work with most transitions due to the nature of their animation (ie move/flip/zoom).
If you need the background to animate while a transition is running, there's a simple trick. Schedule update on a non-CCNode object that has global lifetime (ie AppDelegate). Then manually send the update to all nodes that should continue to update their state during a transition, and only during a transition.
You can register updates on non-node objects like this:
[_director.scheduler scheduleUpdateForTarget:self priority:0 paused:NO];
This update method will be called even during scene transitions. Alternatively it should also be possible to continue updating nodes by changing their paused state and thus resuming scheduler and actions, either by overriding the paused property or by explicitly unpausing specific nodes when a transition occurs.
I'm working on a game right now, in which every second, I want to create x number of new UIImage objects that begin at the top of the screen. After they have been instantiated, I want them automatically to fall down until they reach the bottom of the screen, at which point I no longer have any use for them.
Its almost like raindrops - X number of them are created every second, and they each fall down at different speeds.
I'm getting really confused as to how I would even just design my program to do this.
Any help would be greatly appreciated. Thanks.
Are they all copies of the same image, or at least copies of one among a limited set of images?
For such a game-like, graphics intensive app I would seriously consider using OpenGL ES, although the learning curve is steep for people familiar with UIKit only. Fortunately, there are third party, open-source libraries such as Cocos2d that make efficient 2d graphics almost as easy to code as UIKit.
Regarding your question in particular, I haven't watched the video mentioned by #ctrahey, but I can think of these patterns:
Have a (finite) 'pool' of reusable objects, which size is equal to the maximum amount of instances that might appear on screen at any given time. You definitely want to set this limit, since graphics performance is not infinite. Each time an object falls off-screen, 'reset' its state and reuse it (from the top, again). UITableView does something like this with table cells.
Create the instances on demand, and destroy them (release->dealloc) once they go off-screen.
You have to balance the runtime cost of creating/destroying instances vs. the cost/inconvenience of resetting objects.
Hope it helps
Step 1: Run a piece of code every second
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(dispatchSomeRaindrops) userInfo:nil repeats:YES];
Step 2: Create some particles, send them down the screen, and clean them up when they reach the bottom.
- (void)dispatchSomeRaindrops
{
for (int i = 0; i < 5; i++) {
UIImageView *view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"safari.png"]];
CGFloat halfHeight = view.frame.size.height / 2;
CGFloat x = arc4random() % (int)self.view.frame.size.width;
view.center = CGPointMake(x, -halfHeight);
[self.view addSubview:view];
NSTimeInterval duration = 10 + arc4random() % 10;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
float endY = self.view.frame.size.height + halfHeight;
view.center = CGPointMake(x, endY);
} completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
}
Check out the Core Animation video from WWDC 2011, and near the end there is a bit on "Replicators", and it sounds like exactly what you are after.
FYI: found a terrific resource to handle this exact scenario. It does require you to use cocos2d, but it explains it in a very clear and understandable manner.
http://www.raywenderlich.com/352/how-to-make-a-simple-iphone-game-with-cocos2d-tutorial
I have a view which implements freehand drawing, but I have a small problem. I noticed on the iPad 3 that everything went to hell, so I tried to update my drawing code (probably as I should have done in the first place) to only update the portion that was stroked. However, the first stroke after open, and the first stroke after about 10 seconds of idle are extremely slow. After everything is "warmed up" it is smooth as butter and only takes about 0.15ms per drawRect. I don't know why, but the whole view rectangle is getting marked as dirty for the first drawRect, and the first drawRect after idle (then it takes about 150 ms to update). The stack trace shows that my rectangle is being overridden by CABackingStoreUpdate_
I tried not drawing the layer if the rectangle was huge, but then my entire context goes blank (will reappear as I draw over the old areas like a lotto ticket). Does anyone have any idea what goes on with UIGraphicsGetCurrentContext()? That's the only place I can imagine the trouble is. That is, my views context got yanked by the context genie so it needs to render itself fully again. Is there any setting I can use to persist the same context? Or is there something else going on here...there is no need for it to update the full rectangle after the initial display.
My drawRect is very simple:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = mDrawingLayer ? CGLayerGetContext(mDrawingLayer) : NULL;
if(!mDrawingLayer)
{
c = UIGraphicsGetCurrentContext();
mDrawingLayer = CGLayerCreateWithContext(c, self.bounds.size, NULL);
c = CGLayerGetContext(mDrawingLayer);
CGContextSetAllowsAntialiasing(c, true);
CGContextSetShouldAntialias(c, true);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetLineJoin(c, kCGLineJoinRound);
}
if(mClearFlag)
{
CGContextClearRect(c, self.bounds);
mClearFlag = NO;
}
CGContextStrokePath(c);
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
CGContextDrawLayerInRect(UIGraphicsGetCurrentContext(), self.bounds, mDrawingLayer);
NSLog(#"%.2fms : %f x %f", (CFAbsoluteTimeGetCurrent() - startTime)*1000.f, rect.size.width, rect.size.height);
}
I found a useful thread on on the Apple Dev Forums describing this exact problem. It only exists since iOS 5.0 and the theory is that it is because Apple introduced a double buffering system, so the first two drawRects will always be full. However, there is no explanation for why this will happen again after idle. The theory is that the underlying buffer is not guaranteed by the GPU, and this will be discarded at whim and need to be recreated. The solution (until Apple issues some kind of real solution) is to ping the buffer so that it won't be released:
mDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(pingRect)];
[mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- (void)pingRect
{
//Already drawing
if(mTouchCount > 0) return;
//Even touching just one pixel will keep the buffer alive
[self setNeedsDisplayInRect:CGRectMake(0, 0, 1, 1)];
}
The only weakness is if the user keeps their finger perfectly still for more than 5 seconds, but I think that is an acceptable risk.
EDIT Interesting update. It turns out simply calling setNeedsDisplay is enough to keep the buffer alive, even if it returns immediately. So I added this to my drawRect method:
- (void)drawRect:(CGRect)rect
{
if(rect.size.width == 1.f)
return;
//...
}
Hopefully, it will curb the power usage that this refresh method will surely increase.