views:

1166

answers:

4

I have a view that has a background image and a CGPath that gets changed as the user touches the screen. Drawing the image with CGContextDrawImage() and then drawing the path on top is not fast enough and it hinders the touch event performance. What I would like to have is to have a bitmap buffer and only draw the changes in the path to it. Thus, the view's drawRect() will be just drawing the buffer. I suspect I will need to use CGContexts but I can't quite figure out how to do it. Is there a different way of optimizing this?

Edit:

I am adding to the path every time the user touches the screen; so the "path changes" refers to adding more points to an existing path. This is why I think I can just buffer the image and draw just the "delta" of the path instead of drawing the whole thing each time. At the moment my drawing and event logic are something like this:

-(void)drawRect{
    //Draw the background image
    //Draw the whole path
}

-(void)touchesMoved{
    //Add the touch point to the path
    [self setNeedsDisplay];
}

Drawing the background this way makes touch event sampling perform noticeably worse than drawing just the path.

A: 

Actually that's pretty much the way it works already. The context passed to drawRect draws into a CALayer, which is buffered until you do something that requires the view to redraw itself. You mentioned that the path is changed "as the user touches the screen" - are you then setting setNeedsDisplay on the view?

If you really have a different path every time then you're not going to be able to buffer it. If it's the same, as long as you're not calling setNeedsDisplay, or something that will effect it being called, then the layer that backs your view should be buffered. I have a load of such views in layers, all with lots of fancy CG stuff (drawing and filling paths, clipping images, colourising etc etc) and the performance is great.

If you have different paths (perhaps to represent, visually, the touch) - you might want to think about using alternate views - which themselves don't change - or perhaps using view animation on the single, pre-rendered view?

If none of this hits the mark, please post more information to help us see what you're trying to do.

Phil Nash
+1  A: 

If the background image isn't changing (I can't tell for sure if it is), then you should put the background image in a different CoreAnimation layer, with the layer containing the path on top of it. That lets the GPU handle the compositing, rather than doing it all in your -drawRect. The background image can then be cached when possible, and the only thing you will have to update will be the path (which you still can probably avoid redrawing entirely with each frame). You will also be able to do things like scale or rotate the path without having to worry about the effect on the background image.

A: 

I'm not 100% positive that it works this way on the iPhone, but on the equivalent Mac OS X class, setNeedsDisplay: is effectively synchronous, so expensive drawing operations can kill a loop's performance. Try putting your drawing code on a timer that executes several times per second instead of explicitly calling it in your event-handling code.

See this CocoaBuilder thread for more info.

Chuck
A: 

From your edit it looks like you're doing some sort of drawing app.

In that case I'd try "unknown"'s suggestion (put background image in separate layer/ view from the path). In fact I'd do that anyway. Are you using CGPath? If not it's probably worth doing that too.

If you're still having issues, it might be worth separating out the current "edits" (any lines drawn in quick succession) into their own path (in their own layer/ view), and add them to the main path after an suitable idle period (using a timer).

Phil Nash