Re: Threaded drawing
Re: Threaded drawing
- Subject: Re: Threaded drawing
- From: Graham Cox <email@hidden>
- Date: Fri, 06 Dec 2013 17:46:52 +0100
On 6 Dec 2013, at 2:42 pm, Roland King <email@hidden> wrote:
>
>>
>>> That might be another way, by the way, render to your own tiled small bitmaps on background threads then blit them into the real context.
>>
>> Yep, that was also on my list to try, but for top performance it would make sense not to do that if it can be avoided.
>>
>
> My WAG would be that works well enough. Heavy lifting on the threads, then toss a bitmap/CGImage copy to the main thread where I'd hope it was just a bitblt into the context. I've seen similar recommended for iOS but only with a fullsize bitmap, not with bits and pieces. In most of those cases the view was an image and the contents was constructed on a background thread and then set into the image outside the drawrect call.
OK, I’ve now tried this approach, and it’s much cleaner in that it works with scrollers, with and without “responsive” scrolling (which appears to buffer its contents) and also zooming. Code follows. In this case, drawing overall is slower than the normal case, because the simple drawing I’m doing doesn’t tax things much, so the set up and tear down of the bitmaps is dominating, but I would expect for very complex drawing it would show a win.
I also tried to create one big bitmap into which all the different contexts drew. That worked to a degree, but it seems that when you create a context that wraps a block of memory, the first thing it does it to clear it to some background colour. That’s annoying, because you end up with some blank areas where a later thread cleared the bitmap that an earlier thread had already drawn. If just creating the context didn’t do this clearing, it would probably work fine and be a lot more performant, because the bitmap and image would not need to be created on the fly, and afterwards you can just blit the update rects back to the main context. It would be useful if some graphics gurus (Ken Ferry??) could chip in and comment on whether this conclusion is correct, and if there’s a workaround.
Also, to answer my earlier question, blocks capture the values of variables when they’re created, not when they’re run, so that makes life much easier as well.
The next step will be to make the tiles themselves represent the visible rect of the view, and the context factor in the zoom scale in such a way that drawing paths and so on always rasterizes to the native resolution of the screen. At the moment zooming in shows pixelization because the tile bitmaps are scaling with the view frame. Need some more thought on how to do that.
- (void) drawRect:(NSRect) dirtyRect
{
NSTimeInterval time = [NSDate timeIntervalSinceReferenceDate];
// get current context:
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState( ctx );
const CGRect* rects;
NSInteger count;
[self getRectsBeingDrawn:&rects count:&count];
CGContextClipToRects( ctx, rects, count );
// 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:^
{
// make sure retina screens taken into account
NSRect backing = [[self window] convertRectToBacking:tileRect];
// create a bitmap image for the tile
CGColorSpaceRef cspace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
NSUInteger bytesPerRow = NSWidth( backing ) * 4;
size_t bytes = bytesPerRow * NSHeight( backing );
void* bitsPtr = malloc( bytes );
CGDataProviderRef provider = CGDataProviderCreateWithData( NULL, bitsPtr, bytes, NULL );
CGImageRef tileImage = CGImageCreate( backing.size.width, backing.size.height, 8, 32, bytesPerRow, cspace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast, provider, NULL, false, kCGRenderingIntentDefault);
CGDataProviderRelease( provider );
CGColorSpaceRelease( cspace );
// create a context to draw into
CGContextRef ncx = CGBitmapContextCreateWithData( bitsPtr, backing.size.width, backing.size.height, 8, bytesPerRow, cspace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast, NULL, NULL );
// apply scaling for user space to backing
CGAffineTransform tfm = CGAffineTransformMakeScale( backing.size.width/tileRect.size.width, backing.size.height/tileRect.size.height);
tfm = CGAffineTransformTranslate( tfm, -tileRect.origin.x, -tileRect.origin.y );
CGContextConcatCTM( ncx, tfm );
// draw some content in the tile
[self drawTile:tileRect inContext:ncx];
CGContextFlush( ncx );
CGContextRelease( ncx );
// blit the result back to the main context. This must be serialized.
@synchronized( self )
{
CGContextDrawImage( ctx, tileRect, tileImage );
}
CGImageRelease( tileImage );
free( bitsPtr );
}];
[mDrawingQueue addOperation:op];
#else
[self drawTile:tileRect inContext:ctx];
#endif
}
}
}
#if DRAW_THREADED
[mDrawingQueue waitUntilAllOperationsAreFinished];
#endif
CGContextSetStrokeColorWithColor( ctx, [[NSColor blueColor] CGColor]);
CGContextSetLineWidth( ctx, 1 );
CGContextStrokeRect( ctx, CGRectInset( mSel, 1, 1 ));
CGContextRestoreGState( ctx );
time = [NSDate timeIntervalSinceReferenceDate] - time;
//NSLog(@"drawing time = %f", time);
}
_______________________________________________
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