I'm working on a simple program that has 500 "particles" that have an x and a y coordinate. They move around the screen and respond to touches. As I go past 500 particles the app starts running much slower. Using CPU sampler I discovered that drawing the particles is taking up the most CPU time.
This is the drawing code:
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:red/255 green:green/255 blue:blue/255 alpha:1].CGColor);
CGRect rectangle = CGRectMake(xpos,ypos,9,9);
CGContextAddEllipseInRect(context, rectangle);
CGContextFillPath(context);
red,green,and blue are floats used to change the color of the particles based on their speed, but this isn't the problem.
This is how I was taught to use Quartz and it works just fine for most drawing, but this code is executed 500+ times and the game starts slowing down. I've run the program with CPU sampler with the drawing code commented out and there is hardly any CPU usage despite all the math going on in the background.
Is there a more efficient way to draw circles in iOS?
You can try two different approaches to help speed up performance...
Use prerendered UIImage/CGImage instead of points (won't give you the ability to change colors/sizes dynamically, but maybe you only need a limited range for your app)
Use OpenGL, GL_POINTS
Quartz is generally slower than OpenGL especially for path based drawing from all the research I've done on the IPhone. Refer to the IPhone Dev forums and you'll see a general consensus about this.
Making a layer (CALayer) for each particle might actually make sense. In general, doing drawing "yourself" in -drawRect: is the path to slowness on iOS. Avoid it if at all possible.
Related
How to correctly implement the blocks in the drawing view, so that when they could cut the line in two parts. Using UIImageView or UIImage?
After the cut blocks should fall under the influence of physics.
First, how many cuts could happen in total? How many independent pieces of block could result? 10? 100? Before implementing any of these, test moving that number of objects around on an iPhone or iPod touch. Just because it works on the simulator does not mean it will be fast enough on the actual device.
Second, as already noted, there are libraries for game graphics and physics that may do a lot of the work for you. Cocos2D appears to be a popular option, combining OpenGL drawing with relatively easy access to physics libraries.
Anyway, to do your own drawing, here are the choices:
Move all the graphics into OpenGL. This should not be undertaken lightly - you lose a lot of the ease of working in Cocoa Touch. You also have maximum control over your graphics and animation, and can achieve the smoothest performance if you take the time to optimise it.
Have a single UIView, adding CALayer sublayers to its main layer for every independent block. CALayers are designed for rapid moving and compositing. However, if you're running a physics simulation, your first step will be to remove their animation behavior. This tutorial series may be useful.
Have a separate UIView for each block. This will have similar performance to using CALayers, as UIViews are actually drawn with CALayer. This option will use up more memory, (you have at least as many layers and more views than before), but you have all of the power of CALayers plus a few drawing options that are easier on views.
Have a single UIView, and draw every block during its drawRect method. This may look easy to implement, but it will almost certainly be too slow.
If at all possible, test each of these. Before you continue with the cutting and physics parts, how many blocks can you animate across the screen before it slows down too far? Can you make a game with that Remember that your physics system will slow the game down when it does work.
I am working on an iOS App that visualizes data as a line-graph. The graph is drawn as a CGPath in a fullscreen custom UIView and contains at most 320 data-points. The data is frequently updated and the graph needs to be redrawn accordingly – a refresh rate of 10/sec would be nice.
So far so easy. It seems however, that my approach takes a lot of CPU time. Refreshing the graph with 320 segments at 10 times per second results in 45% CPU load for the process on an iPhone 4S.
Maybe I underestimate the graphics-work under the hood, but to me the CPU load seems a lot for that task.
Below is my drawRect() function that gets called each time a new set of data is ready. N holds the number of points and points is a CGPoint* vector with the coordinates to draw.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// set attributes
CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGContextSetLineWidth(context, 1.f);
// create path
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddLines(path, NULL, points, N+1);
// stroke path
CGContextAddPath(context, path);
CGContextStrokePath(context);
// clean up
CGPathRelease(path);
}
I tried rendering the path to an offline CGContext first before adding it to the current layer as suggested here, but without any positive result. I also fiddled with an approach drawing to the CALayer directly but that too made no difference.
Any suggestions how to improve performance for this task? Or is the rendering simply more work for the CPU that I realize? Would OpenGL make any sense/difference?
Thanks /Andi
Update: I also tried using UIBezierPath instead of CGPath. This post here gives a nice explanation why that didn't help. Tweaking CGContextSetMiterLimit et al. also didn't bring great relief.
Update #2: I eventually switched to OpenGL. It was a steep and frustrating learning curve, but the performance boost is just incredible. However, CoreGraphics' anti-aliasing algorithms do a nicer job than what can be achieved with 4x-multisampling in OpenGL.
This post here gives a nice explanation why that didn't help.
It also explains why your drawRect: method is slow.
You're creating a CGPath object every time you draw. You don't need to do that; you only need to create a new CGPath object every time you modify the set of points. Move the creation of the CGPath to a new method that you call only when the set of points changes, and keep the CGPath object around between calls to that method. Have drawRect: simply retrieve it.
You already found that rendering is the most expensive thing you're doing, which is good: You can't make rendering faster, can you? Indeed, drawRect: should ideally do nothing but rendering, so your goal should be to drive the time spent rendering as close as possible to 100%—which means moving everything else, as much as possible, out of drawing code.
Depending on how you make your path, it may be that drawing 300 separate paths is faster than one path with 300 points. The reason for this is that often the drawing algorithm will be looking to figure out overlapping lines and how to make the intersections look 'perfect' - when perhaps you only want the lines to opaquely overlap each other. Many overlap and intersection algorithms are N**2 or so in complexity, so the speed of drawing scales with the square of the number of points in one path.
It depends on the exact options (some of them default) that you use. You need to try it.
tl;dr: You can set the drawsAsynchronously property of the underlying CALayer, and your CoreGraphics calls will use the GPU for rendering.
There is a way to control the rendering policy in CoreGraphics. By default, all CG calls are done via CPU rendering, which is fine for smaller operations, but is hugely inefficient for larger render jobs.
In that case, simply setting the drawsAsynchronously property of the underlying CALayer switches the CoreGraphics rendering engine to a GPU, Metal-based renderer and vastly improves performance. This is true on both macOS and iOS.
I ran a few performance comparisons (involving several different CG calls, including CGContextDrawRadialGradient, CGContextStrokePath, and CoreText rendering using CTFrameDraw), and for larger render targets there was a massive performance increase of over 10x.
As can be expected, as the render target shrinks the GPU advantage fades until at some point (generally for render target smaller than 100x100 or so pixels), the CPU actually achieves a higher framerate than the GPU. YMMV and of course this will depend on CPU/GPU architectures and such.
Have you tried using UIBezierPath instead? UIBezierPath uses CGPath under-the-hood, but it'd be interesting to see if performance differs for some subtle reason. From Apple's Documentation:
For creating paths in iOS, it is recommended that you use UIBezierPath
instead of CGPath functions unless you need some of the capabilities
that only Core Graphics provides, such as adding ellipses to paths.
For more on creating and rendering paths in UIKit, see “Drawing Shapes
Using Bezier Paths.”
I'd would also try setting different properties on the CGContext, in particular different line join styles using CGContextSetLineJoin(), to see if that makes any difference.
Have you profiled your code using the Time Profiler instrument in Instruments? That's probably the best way to find where the performance bottleneck is actually occurring, even when the bottleneck is somewhere inside the system frameworks.
I am no expert on this, but what I would doubt first is that it could be taking time to update 'points' rather than rendering itself. In this case, you could simply stop updating the points and repeat rendering the same path, and see if it takes nearly the same CPU time. If not, you can improve performance focusing on the updating algorithm.
If it IS truly the problem of the rendering, I think OpenGL should certainly improve performance because it will render all 320 lines at the same time in theory.
I have a game with a number of animated "monsters". The animation is made with ~20 png images for each monster. So I use UIImageView with setted animationImages:.
The problem is that sometimes there can be a lot of monsters on the screen (up to 110 in total and up to 10 different). So when all of them are on the screen at the same time - I see animation problems (very low fps).
Please, can you give me some advice - how can I solve this problem?
You can use CoreAnimation as described in this tutorial. It explains pretty well all the techniques you can use to increase the performance from where you are now (first of all it doesn't use UIViews and the standard animationImages, second it makes use of sprites (also called texture atlases) which will not only increase performance but also will make your life a lot more easier when it comes to managing the image resources).
Also you can use CADisplayLink to create a game loop in which you can make all the updates. There are several questions/answers here on SO that describe just that.
My code goes as following -
[[NSColor whiteColor] set];
// `path' is a bezier path with more than 1000 points in it
[path setLineWidth:2];
[path setLineJoinStyle:NSRoundLineJoinStyle];
[path stroke];
// some other stuff...
Running the time profiling tool in Instruments it tells me my app is spending 93.5% of the time doing the last line [path stroke], and Quartz Debugger tells me my app is only running at less than 10 fps (another view changing position on top of it is always causing the update).
I'm looking for ways to improve the performance of stroking the bezier path, sometimes paths with more than 1000 points draws very quickly with >60fps, however in some extreme cases even with the same number of points, perhaps if the points are too far from each other (or too dense?) the performance becomes really sluggish.
I'm not sure what I can do about this. I think caching the view as a bitmap rep is helpful, but it can't really help with live resize.
Edit: commenting out the line [path setLineWidth:2]; certainly helps, but the path looked really too 'thin'.
You can adjust the flatness of the curve using the method setFlatness:, higher values increase the rendering speed at the expense of accuracy. You should use a higher value during live resizes, for example.
Back when I asked the Quartz 2D team at Apple about this, they told me that the biggest factor in the performance of stroking a path with a large number of points, is how many times the path intersects itself. There's a lot of work that goes into properly anti-aliasing an intersection.
Do you build the path at each draw - does it change from drawing to drawing? It sounds like it does change. There may be caching if you draw the same path over and over, so try creating it and keeping it around until it changes. It may help.
You can also drop down an API level or two. Perhaps CALayer objects may do what you want. In other words - do you really have a 1000 point line that needs to be curved to connect the points? You can do a bunch of CALayer objects to draw line segments.
The math on these processors is fast. You could also perhaps write a math routine to throw out unneeded points, to cut the number from 1000 to about 200 or so, say. The math would try to eliminate points that are close together, etc.
My bet is on the math to throw out points that don't make any visual difference. The flatness thing sounds interesting too - it may be that by going totally flat you are doing line segments.
What do you mean when you say
another view changing position on top
of it is always causing the update
??
Do you mean that you are not redrawing the view at 60fps? Then that will be why you are not seeing 60fps then.
When
Instruments it tells me my app is
spending 93.5% of the time
doing something, it doesn't mean 'all available' time it means 93.5% of the cpu cycles your app consumed. ie, it could be no time at all. It isn't enough to determine that you need to optimise. I'm not saying you don't need to, or that stroking massive beziers isn't dog slow. Just that alone doesn't mean much.
I have a game that's moving fewer than 10 small animated UIImageViews at once, maximum. I'm driving their animation with a CADisplay timer running at 60fps. Here is an example of how I move the views in my update method:
// for each insect in insectArray
insectView.center = insect.hitCenter // I pull a position from my model object
The graphics are 32 x 32 pixels with up to 5 animation frames each, if that helps. They have an alpha channel for transparency. I've profiled and eliminated any in-game calculations as a bottleneck. I've also made the opacity property = YES, for a very small speedup. Having the animated frames playing or not makes no difference.
The frame rates are mostly great, except on older devices like the iPhone 1G and 3G. There I get intermittent stuttering.
Before switching to OpenGL, is there any way to get a bit more performance?
I experienced the same kind of bottleneck with CoreAnimation; it is very limited in terms of system complexity that you can display with decent performance. From what I have read and discussed with others, there is no silver bullet for you (or me) there, sorry!
My usage was actually quite close to yours (no animated frames, though), and using OpenGL ES made it go from painfully sluggish to perfectly snappy, so there's hope for you!
CoreAnimation isn't designed for frame-by-frame animation, you tell it a few keyframes and times, and it will do the rest for you. Why not switch to OpenGL? You can't support the old devices forever...
I agree with FX that there is no silver bullet, but if you provided a little more code, we could make some specific suggestions. Here are a few general ones:
Don't round corners using -setCornerRadius on the UIImageView's layer. You'd never believe how much this can degrade performance
If you're using drop shadows behind your view, make sure you specify a shadow path on the layer as well.
Try turning -shouldRasterize on on the UIImageView layer: [[insectView layer] setShouldRasterize:YES];
Hate to say it, but after this, as others have said, OpenGL is the only other choice.