On 4 Dec 2013, at 9:33 pm, Graham Cox <graham....@bigpond.com> 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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to