Re: Threaded drawing
Re: Threaded drawing
- Subject: Re: Threaded drawing
- From: Graham Cox <email@hidden>
- Date: Fri, 06 Dec 2013 11:26:11 +0100
On 4 Dec 2013, at 9:33 pm, Graham Cox <email@hidden> wrote:
> But that leaves those annoying cases when you have the whole view to redraw. I wondered if it would be worth dividing up the view into rects and rendering each one on a separate thread. The problem seems to me to be that they’d all be drawing into the same CGContext, and I wonder how well that could work - e.g. one thread could set a clip ready for its next drawing operation and another could then change that clip so they’d all be tripping over each other, even though they were all drawing into a different part of the context. If access to the context were synchronised, then that would end up serialising all the drawing so there wouldn’t be any gain.
>
> Has anyone trod this path? It would be useful to know whether there’s anything that can be done along these lines, because rendering 10,000 or more objects is just taking too darn long!
OK, after some thought and a bit of experimentation, I think I’ve got a handle on this. I just thought I’d share with the list in case anyone is remotely interested, or has anything to suggest/add/criticise…
Obviously, you cannot share a graphics context across multiple threads for the reasons I mentioned - a single context cannot be thread safe, by design. But that doesn’t mean you can’t have a separate context per thread, all drawing into the same backing store. That was my breakthrough insight I guess, for what it’s worth.
So proceeding on that basis, I wrote a test view that uses a NSOperationQueue to dispatch chunks of drawing work by dividing up the view into tiles, creating a context for each tile (but all drawing into the same window backing store). As long as you wait for the tiles to all finish, everything works just tickety-boo.
Here’s the code, which is the -drawRect: method of a view. The view creates an NSOperationQueue called mDrawingQueue in its -initWithFrame: method.
#define TILE_SIZE NSMakeSize( 100, 100 )
#define DRAW_THREADED 1
- (void) drawRect:(NSRect) dirtyRect
{
[[NSColor whiteColor] set];
NSRectFill( dirtyRect );
// get current context:
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
// divide into tiles
NSSize tileSize = TILE_SIZE;
NSRect br = self.bounds;
NSUInteger tx, ty;
tx = ceil(NSWidth( br ) / tileSize.width);
ty = ceil(NSHeight( br ) / tileSize.height);
NSRect tileRect;
tileRect.size = tileSize;
for( NSInteger i = 0; i < ty; ++i )
{
for( NSInteger j = 0; j < tx; ++j )
{
tileRect.origin.x = br.origin.x + tileSize.width * j;
tileRect.origin.y = br.origin.y + tileSize.height * i;
if([self needsToDrawRect:tileRect])
{
#if DRAW_THREADED
NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^
{
NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithWindow:[self window]];
CGContextRef ncx = [context graphicsPort];
// align and clip this new context to the view
CGAffineTransform ctm = CGContextGetCTM( ctx );
CGContextConcatCTM( ncx, ctm );
CGContextClipToRect( ncx, br );
// also clip to the tile (not strictly necessary)
CGContextClipToRect( ncx, tileRect );
[self drawTile:tileRect inContext:ncx];
}];
[mDrawingQueue addOperation:op];
#else
[self drawTile:tileRect inContext:ctx];
#endif
}
}
}
#if DRAW_THREADED
// need to wait until tiles all drawn before flushing
[mDrawingQueue waitUntilAllOperationsAreFinished];
#endif
}
- (void) drawTile:(NSRect) tile inContext:(CGContextRef) ctx
{
// draw something in the tile. Not very challenging at the moment
NSRect tr = NSInsetRect( tile, 10, 10 );
CGContextSetFillColorWithColor( ctx, [[NSColor redColor] CGColor]);
CGContextFillRect( ctx, tr );
CGContextSetStrokeColorWithColor(ctx, CGColorGetConstantColor( kCGColorBlack ));
CGContextStrokeRect( ctx, tile );
}
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden