On 6 Dec 2013, at 2:42 pm, Roland King <[email protected]> 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 protected])
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 [email protected]