Performance with frequent drawing CGPaths

I am working on an iOS application that renders data as a line graph. The graph is drawn as CGPath in a full-screen user-defined UIView and contains no more than 320 data points. The data is often updated, and the schedule needs to be redrawn accordingly - the refresh rate of 10 / sec will be pleasant.

So far so easy. It seems, however, that my approach takes a lot of CPU time. Updating a graph with 320 segments at 10 times per second results in 45% processor utilization for the process on the iPhone 4S.

Perhaps I underestimate the graphics work under the hood, but for me there is a lot of CPU load.

Below is my drawRect() function, which is called every time a new dataset is ready to work. N contains the number of points, and points the CGPoint* vector with coordinates for drawing.

 - (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 passing the path to the standalone CGContext before adding it to the current layer, as suggested here , but without any positive result. I also looked for an approach to CALayer directly, but that didn't matter either.

Any suggestions for improving performance for this task? Or is the rendering just more for the processor, which I understand? Will OpenGL make sense / difference?

Thanks / Andi

Update: I also tried using UIBezierPath instead of CGPath . This post here gives a nice explanation of why this did not help. Fine-tuning CGContextSetMiterLimit et al. also did not bring much relief.

Update # 2: I ended up switching to OpenGL. It was a steep and frustrating learning curve, but productivity gains are simply unbelievable. However, CoreGraphics smoothing algorithms do a nicer job than what can be achieved with 4x multisampling in OpenGL.

+13
ios objective-c core-graphics cgpath
Jan 03 '12 at 16:57
source share
5 answers

This post here gives a good explanation of why this did not help.

This also explains why your drawRect: method is slow.

Each time you draw, you create a CGPath object. You do not need to do this; you only need to create a new CGPath object each time you change the set of points. Move the CGPath creation to a new method that you only call when you change the set of points, and save the CGPath object between calls to this method. Have a drawRect: just get it.

You have already found that rendering is the most expensive thing you do, and thatโ€™s good: you cannot make rendering faster, can you? Indeed, drawRect: ideally should not do anything other than rendering, so your goal should be to minimize the time spent on rendering by up to 100%, which means moving everything else, as much as possible, from the drawing code.

+7
Jan 03 '12 at 18:59
source share

Depending on how you make your path, it may be that drawing 300 separate paths is faster than a single path with 300 points. The reason for this is that often the drawing algorithm will look to determine the overlapping lines and how to make the intersections โ€œperfectโ€ โ€”when you probably want the lines to overlap opaquely. Many overlap and intersection algorithms have N ** 2 or so in complexity, so the drawing speed scales with the square of the number of points in one path.

It depends on the specific parameters (some of them by default) that you use. You need to try it.

+4
Feb 27 '12 at 16:08
source share

Have you tried using UIBezierPath instead? UIBezierPath uses CGPath under the hood, but it would be interesting to see if the performance is different for some subtle reason. From Apple Documentation :

To create paths in iOS, it is recommended to use UIBezierPath instead of CGPath functions, if you do not need some of the features that only Core Graphics provides, for example, adding ellipses to paths. For more information about creating and visualizing paths in UIKit, see "Drawing shapes Using Bezier paths."

I would also try setting various properties in CGContext, in particular for different line-combining styles, using CGContextSetLineJoin() to find out it doesn't matter.

Did you profile your code with the Time Profiler tool in tools? This is probably the best way to find where the performance bottleneck occurs, even when the bottleneck is somewhere inside the system frameworks.

0
Jan 03 2018-12-12T00:
source share

I am not an expert in this, but at first I would doubt that updating the โ€œpointsโ€ may take time, not rendering. In this case, you can simply stop updating the points and repeat the rendering of the same path, and see if it needs almost the same processor time. If not, you can improve performance by focusing on the update algorithm.

If this is really a rendering problem, I think that OpenGL should certainly improve performance, because it will display all 320 lines at the same time in theory.

0
Jan 03 2018-12-12T00:
source share

I did this using Metal in my project. Obviously, there is a certain difficulty in using metal, so depending on your performance requirements, it may or may not be suitable.

With Metal, all work is done on the GPU, so CPU usage will be near zero. On most iOS devices, you can make several hundred or several thousand curves smoothly, at 60 FPS.

Here is an example of the code I wrote, this can serve as a good starting point for someone: https://github.com/eldade/ios_metal_bezier_renderer

0
Nov 19 '16 at 20:48
source share



All Articles