Reputation: 1393
I am using Quartz 2D to make a simple multi-touch drawing iPad game. The game requires me to draw a new stroke at the finger position every 1/30th of a second.
As far as I know, there is basically no way to get drawRect() to not clear the context every time it is called (self.clearsContextBeforeDrawing = NO; does not work), so my solution was to create a back buffer bitmap (or layer, I can use both), draw every new small stroke into that back buffer every iteration for each finger, and then copy the buffer into the screen every call to drawRect(). In other words:
backlayer = CGLayerCreateWithContext(context, CGSizeMake(W, H), NULL);
offctx = CGLayerGetContext (backlayer);
and then
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
//code here to draw small strokes from old finger position to new one
CGContextDrawLayerInRect(context, [self bounds], backlayer);
}
This worked without problems while I was testing on my iPad 2, but yesterday I noticed that this same code runs much slower on the new iPad 3. The performance is abysmal, slowing my game down from 30FPS all the way to about 5 or so, probably due to the larger, retina display. I have the same problem if I use a separate CGBitmapContext that I create, and then every iteration I create an ImageRef from it and paint it with CGContextDrawImage.
What approach could I take to address this? It seems like I must redraw everything every iteration since it's not good enough to even pass a small rectange to drawRect of what has changed (since every iteration there would need to be several rectangles for each finger)
Thank you
Upvotes: 1
Views: 1735
Reputation: 1393
I managed to resolve this as follows:
I create a new UIView subclass header and implementation files:
@interface fingerView : UIView {
}
Then in my main view, in header I declare 5 of these views:
fingerView* fview[5];
and in my main view implementation I create 5 views of this instance, one for each finger separately. Also, must make sure to make them, enable multitouch for each of them, and make sure that clearsContextBeforeDrawing is set to NO, as we will be updating tiny rects in each of them at a time, and we dont want the system to clear our work.
for(int i=0;i<5;i++) {
fview[i] = [[pView alloc] initWithFrame:topFrame];
[self addSubview: fview[i]];
[self sendSubviewToBack: fview[i]];
fview[i].opaque= NO;
fview[i].clearsContextBeforeDrawing = NO;
fview[i].multipleTouchEnabled = YES;
}
Now inside every finger view keep a large array (i use a simple array, say 10,000 long) of x and y positions that the finger had drawn on. Whenever a finger moves, the main view detects it, and calls a [fview[i] updatePos(newx, newy)], and crucially, we will command the view to only update a tiny potion of itself around these coordinates:
[fview[i] setNeedsDisplayInRect: fingerRect];
where fingerRect is a small rect centered at (newx, newy). Inside the drawRect method for every finger view,
- (void)drawRect:(CGRect)rect
{
if (movep==0) return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, r, g, b, 1);
CGContextSetLineWidth(context, linewidth);
//paint finger
CGContextBeginPath(context);
CGFloat slack= 15;
CGFloat minx= CGRectGetMinX(rect)-slack;
CGFloat maxx= CGRectGetMaxX(rect)+slack;
CGFloat miny= CGRectGetMinY(rect)-slack;
CGFloat maxy= CGRectGetMaxY(rect)+slack;
bool drawing = NO;
for(int i=0;i<movep;i++) {
CGFloat xx= x[i];
CGFloat yy= y[i];
if(xx>minx && xx<maxx && yy>miny && yy<maxy) {
if(drawing) {
// continue line
CGContextAddLineToPoint(context, xx, yy);
CGContextMoveToPoint(context, xx, yy);
} else {
// start drawing
CGContextMoveToPoint(context, xx, yy);
drawing= YES;
}
} else {
drawing= NO;
}
}
CGContextStrokePath(context);
and also, as I mentioned
- (void)updatePos: (CGFloat)xnew: (CGFloat) ynew
{
x[movep]= xnew;
y[movep]= ynew;
movep= movep+1;
Hopefully you can figure out how this works. Every view will look into this rectangle that has been modified, and checks all finger positions that went around that rect, and only draws those. This will come down to very few strokes, and so the entire code runs very fast.
The lesson overall is that UIViews are extremely optimized. As much as possible, try to make a whole bunch of them, update them only locally if at all, and let Apple's magic blend it all together.
Upvotes: 2