We're working on a couple simple games and we have some performance problems that we keep running into, and I was curious if it was a code issue or a "we're using the wrong objects" issue.
Our games are basically simple ones that work as follows:
We have a custom view that we load, and a custom object that is our game engine. The view has a timer that fires like such:
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(animationTimerMethod) userinfo:nil repeats:YES];
and in our timer method, we have the following:
- (void)animationTimerMethod:(NSTimer*)timer
{
if([gameEngine doGameLoop]) //only redraw if we need to
[self setNeedsDisplay];
}
Our drawing code is very simple, we're just drawing a couple of images on the screen, using code similar to the following:
- (void)drawRect:(CGRect)rect
{
CGGraphicsContext ctx = UIGraphicsGetCurrentContext();
CGContextDrawImage(ctx, someRect, someImage);
}
The main problem we have is responsiveness to touch. Our code will not get a response in the touchesBegan method many times. Is this just a limitation of the Core Graphics library? Should we be using OpenGL? Are there other ways to do simple animations (not even objects moving on screen, just 6 or so frames of animation for an object in the same place) that would be more responsive?
Thanks so much!
There are definitely ways to speed this up. Manually redrawing the images every frame means you are redrawing textures you could be reusing. You could reuse them either by moving to OpenGL, or moving the images into a CALayer and then just repositioning the layer instead of redrawing the image.
If you do not want to move to OpenGL you would probably also see a significant performance win by moving to CAAnimation instead of having your code calculate each frame, but that might require significant changes to your engine.
Also, if you can avoid avoid alpha compositing it is a big speed up.
My first question is do your images have transparency?
Images with transparency with HIGHLY effect performance.
In Apple's doc's they make reference to this point several times.
The second thing I would ask is, have you tried running your app in Instruments and/or Shark? Both these apps can help give you an idea of where your problems are happening.
Related
Within my iOS app, I have a uiview that needs to be animated, transformed with gestures, shaded (using quartzcore shadows), and edited. When I perform animations and gestures on this UIView it is extremely "laggy". The animations aren't very "laggy" on the iPhone, however when using the iPad the animations become almost unresponsive (to the point where it seems like my app is crashing). I've tested my app using Instruments, and the app isn't taking up much memory / CPU / power until the animations begin. I have tested both on the device and on my Intel i7 8GB iMac and the animations are "laggy" on both.
The animation I am performing is nothing complex, it is simply a translation across the X Axis. After looking through every line of code related to the animation, I found that these lines are the issue(s):
viewer.layer.masksToBounds = NO;
viewer.layer.shadowOffset = CGSizeMake(-1, 1);
viewer.layer.shadowRadius = 3;
viewer.layer.shadowOpacity = 0.3;
The above code adds a shadow to the view that lags whenever I animate it (viewer). If I use the above code, but I add the following line animations work nicely:
viewer.layer.shouldRasterize = YES;
The problem with this code is that is seriously decreases the quality of the content displayed inside of the UIView (viewer). Here's an image with shouldRasterize set to YES:
Then the UIView without shouldRasterize:
Those screenshots are both from the same Retina iPad.
The ultimate question: How can I smoothly perform animations on a UIView with shadows (preferably using QuartzCore)? Is there a way to rasterize the content without degrading its quality?
The shadow properties on CALayer can be very inefficient while animating because it requires recalculating the shadow on every frame based on the contents of the layer. Luckily, the expensive part is the shadow path calculation, so if you just create a CGPath representing the shape of your content and assign it to layer.shadowPath then performance will skyrocket.
Since your layer seems to be completely filled with opaque data, the shadowPath is pretty simple:
layer.shadowPath = [UIBezierPath bezierPathWithRect:(CGRect){CGPointZero, layer.bounds.size}].CGPath;
The only downside is you'll need to edit this whenever the size of the layer changes.
Im trying to rotate a CALayer with various sublayers according to time. There is UDP Multicast receiver in place which will receive new timecodes. I fetch new times via a timer:
[NSTimer scheduledTimerWithTimeInterval:0.02f
target:self
selector:#selector(fetchNewTimeAndRotate)
userInfo:nil
repeats:YES];
The CALayer should be rotated according to time. In my case, one rotation in 1.8 seconds. Actually i dont need any animation, i just need the angle to be set very often, so that it produces the actual animation.
I´ve tried setting the layers rotation in various ways:
1st i tried via CATransform3DMakeRotation:
CATransform3D rotation1 = CATransform3DMakeRotation([self DegreesToRadians:newAngle], 0.0f, 0.0f, 1.0f);
self.circleLayer.transform=rotation1
2nd try was using an instant rotation via an animation:
rotation.fromValue = [NSNumber numberWithFloat:[self DegreesToRadians:oldAngle]];
rotation.toValue = [NSNumber numberWithFloat:[self DegreesToRadians:newAngle]];
rotation.duration = 0.0f;
rotation.repeatCount = 0.0f;
rotation.removedOnCompletion = NO; //also used YES here with no effect
[self.circleLayer addAnimation:rotation forKey:#"transform.rotation.z"];
oldAngle=newAngle;
3rd try was just setting an angle to "transform.rotation.z"
[self.circleLayer setValue:[NSNumber numberWithFloat:[self DegreesToRadians:newAngle]] forKeyPath:#"transform.rotation.z"];
All of the above approaches do work, but lead to significant stuttering in the rotation. I´ve tried using several different timings in fetching and in animation-length. Nothing seems to get rid of the problem.
Using an autorotation every 0.02 and an 3.6° angle is the only option that presented smooth results for me. Am i making a fundamental mistake here or didn´t understand the concept of CoreAnimation? I know that it is to be used to make the animation itself. But i need it to react to user input and change immediatly.
Im thankful for any help.
You mean CADisplayLink, don't you?
DisplayLink objects are commonly used for frame-based animation in OpenGL, but they do not "use OpenGL". It's perfectly valid to create a display link and attach it to your application to trigger drawing.
As the other poster said, NSTimer is not a good choice for fine control of animation frames. If you write a program that just runs a timer and measures it, it will look good. However, timers depend on your app visiting the event loop frequently. They are not preemptive, and if your app is busy when the timer should fire, it will be off, or even miss a firing interval.
If you continue to use a display link, you might want to write your code to check the time elapsed from the last frame and calculate the amount to animated baed on that. That way if you drop a frame the animation still proceeds at the correct velocity (If you drop a frame then twice as much time elapses before the next frame, so you make your objects move twice as much.)
I am using CADisplayLink to draw frames using the EAGLView method in a game at 60 times per second.
When I call UIView animateWithDuration: the framerate drops down to exactly half, from 60 to 30 fps for the duration of the animation. Once the animation is over, the fps rises instantly back up to 60.
I also tried using NSTimer animation method instead of CADisplayLink and still get the same result.
The same behavior happens when I press the volume buttons while the speaker icon is fading out, so it may be using animateWithDuration. As I would like to be able to handle the speaker icon smoothly in my app, this means I can't just rewrite my animation code to use a different method other than animateWithDuration, but need to find a solution that works with it.
I am aware that there is an option to slow down animations for debug on the simulator, however, I am experiencing this on the device and no such option is enabled. I also tried using various options for animateWithDuration such as the linear one and the user interaction, but none had an improvement.
I am also aware I can design an engine that can still work with a frame rate that varies widely. However, this is not an ideal solution to this problem, as high fps is desirable for games.
Has someone seen this problem or solved it before?
The solution to this is to do your own animation and blit during the CADisplayLink callback.
1) for the volume issue, put a small volume icon in the corner, or show it if the user takes some predefined touch action, and give them touch controls. With that input you can use AVAudioPlayer to vary the volume, and just avoid the system control altogether. you might even be able to determine the user has pressed the volume buttons, and pop some note saying do it your way. This gets you away from any animations happening by the system.
2) When you have an animation you want to do, well, create a series of images in code (either then or before hand), and every so many callbacks in the displayLink blit the image to the screen.
Here's an old thread that describes similar drops in frame rate. In that case, the cause of the problem was adding two or more semi-transparent sprites, but I'd guess that any time you try to composite several layers together you may be doing enough work to cut the frame rate, and animateWithDuration very likely does exactly that kind of thing.
Either use OpenGL or CoreAnimation. They are not compatible.
To test this remove any UIView animation, the frame rate will be what you expect. Add back UIView animation, it will drop to 30fps.
You said:
When I call UIView animateWithDuration: the framerate drops down to exactly half, from 60 to 30 fps for the duration of the animation. Once the animation is over, the fps rises instantly back up to 60
I dont know why your not accepting my answer, this is exactly what happens when you combine UIView animation with CA animation not using a UIView.
I am drawing a graph with UIKIT and I want to show before it is finished a preloader (spinning circle)
Another solution should be before start drawing set the alpha of my view to 0.0 and when it's done fade it to 1.0.
Someone did this before and/or know how to do this?
If using UIKit, you could draw the path, fill and stroke, then draw another path. This is resource-intensive as you will be calling drawRect: often.
The other way is to create animation. I'll let someone else as I have no experience in creating animation in iOS.
You could add an activity indicator to the view, send the view setNeedsDisplay and remove the activity indicator at the end of the drawRect:, but I doubt you will see the activity indicator at all. Activity indicators are only useful when you are performing some lengthy operations, taking at least several seconds to complete. If drawing the graph is that long, you should either optimize it or consider drawing it on a secondary thread into a CGImage. In the latter case, I'm afraid, you restrict yourself to Core Graphics.
I would like to create a custom NSView that takes a layered approach to painting. I imagine the majority of the layers would be the same width and height as the backing view.
Is it appropriate to use the Core Animation classes like CALayer for this task, even though I don't expect to need much animation? Is there a more appropriate approach?
To clarify, the view is not meant to be like a canvas in a Photoshop-like application. It more of a data display that should allow for user interaction (selecting, moving, scrolling, etc.)
If it's display and layout you're after, I'd say that a CALayer-based architecture is a good choice. For the open source Core Plot framework, we construct all of our graphs and plot elements out of CALayers, and organize them in a regular hierarchy. CALayers are lightweight and use almost identical APIs between Mac and iPhone. They can even be made to respond to touch or mouse events.
For another example of a CALayer-based user interface, my iPhone application's entire equation entry interface is composed of CALayers, including the menu that slides up from below. Performance is slightly better than that of my previous UIView-based implementation, but the same code also works within my preliminary desktop version of the application.
For a drawing program, I would imagine it would be important to hold a buffer of the bitmap data. The only issue with using a CALayer is that the contents property is a CGImageRef. To turn that back into a graphics context for doing further drawing can be a bit of a pain. You'd have to initialize a new context, draw the bitmap data into it, then do whatever drawing operations you wanted to do, and finally turn that back into a CGImageRef. You probably wouldn't be able to avoid doing a number of pretty large memory allocations, which is virtually guaranteed to slow your program way down.
I would consider holding an off-screen buffer for each layer. Take a look at the Quartz CGLayerRef object. I think it probably does what you want to do: it's an off-screen buffer that holds things you might want to draw repeatedly. You can also quickly get a CGContextRef whenever you need it so you can do additional drawing. And you can always use that CGContextRef with NSGraphicsContext if you want to use Cocoa drawing methods.