Why is renderInContext so much slower than drawing to the screen?

I have a fairly large CAShapeLayer

one that I display. The layer is completely static, but it is contained in UIScrollView

, so it can move and scale - in general, it needs to be redrawn from time to time. In an attempt to improve the frame rate of this scrolling, I set shouldRasterize = YES

to layer, which worked great. Since I never change the properties of this layer, it never gets rasterized skips and I get a solid 60fps. High fives all around, right?

For now, the layer is a little larger. Eventually - and it shouldn't take long - the rasterized image gets too big for the GPU. On my console, <Notice>: CoreAnimation: surface 2560 x 4288 is too large

and it just doesn't draw anything to the screen. I really don't blame it - 2560 x 4288

quite large, but I spent some time scratching my head before I noticed it on the device console.

Now, my question is, how can I get around this limitation? How do I rasterize a really large layer?

The obvious solution is to split the layer into several sublayers, for example, one for each quadrant, and rasterize each of them independently. Is there an easy way to do this? Can I create a new layer that displays a rectangular area from another layer? Or is there another solution that I should investigate?

Edit

Tiled compositing appears to have very poor performance because the layers are re-rasterized every time they enter the screen, creating very sharp scrolling. Is there a way to cache these rasterizations? Or is this the wrong approach altogether?

Edit

Ok, here is my current solution: render the layer once CGImageRef

. Create multiple layers of tiles using the sub-rectangles from this image and actually place them on the screen.

- (CALayer *)getTiledLayerFromLayer:(CALayer *)sourceLayer withHorizontalTiles:(int)horizontalTiles verticalTiles:(int)verticalTiles
{
    CALayer *containerLayer = [CALayer layer];
    CGFloat tileWidth = sourceLayer.bounds.size.width / horizontalTiles;
    CGFloat tileHeight = sourceLayer.bounds.size.height / verticalTiles;

    // make sure these are integral, otherwise you'll have image alignment issues!
    NSLog(@"tileWidth:%f height:%f", tileWidth, tileHeight);

    UIGraphicsBeginImageContextWithOptions(sourceLayer.bounds.size, NO, 0);
    CGContextRef tileContext = UIGraphicsGetCurrentContext();
    [sourceLayer renderInContext:tileContext];
    CGImageRef image = CGBitmapContextCreateImage(tileContext);
    UIGraphicsEndImageContext();

    for(int horizontalIndex = 0; horizontalIndex < horizontalTiles; horizontalIndex++) {
        for(int verticalIndex = 0; verticalIndex < verticalTiles; verticalIndex++) {
            CGRect frame = CGRectMake(horizontalIndex * tileWidth, verticalIndex * tileHeight, tileWidth, tileHeight);
            CGRect visibleRect = CGRectMake(horizontalIndex / (CGFloat)horizontalTiles, verticalIndex / (CGFloat)verticalTiles, 1.0f / horizontalTiles, 1.0f / verticalTiles);
            CALayer *tile = [CALayer layer];
            tile.frame = frame;
            tile.contents = (__bridge id)image;
            tile.contentsRect = visibleRect;
            [containerLayer addSublayer:tile];
        }
    }

    CGImageRelease(image);

    return containerLayer;
}

      

This works great ... sort of. On the one hand, I get 60fps and layer scaling 1980 x 3330

on iPad retina. On the other hand, it takes 20 seconds to start up! So while this solution solves my original problem, it gives me a new one: how can I generate tiles faster?

Literally all the time is spent on the challenge [sourceLayer renderInContext:tileContext];

. This seems odd to me, because if I just add this layer directly, I can render it about 40 times per second, according to Core Animation Instrument. Is it possible that creating my own image context is causing it to not use the GPU or something?

+3


source to share


1 answer


Tiling the layer is the only solution. However, you can implement it in different ways. I suggest doing it manually (creating layers and sublayers as you see fit), but many recommend using the CATiledLayer http://www.mlsite.net/blog/?p=1857 which usually means that maps are usually implemented - scaling and rotation is pretty just with that. CATiledLayer tiles are loaded (drawn) on demand as soon as they are entered on the screen. This implies a short delay (blinking) before the tile is fully drawn, and AFAIK it is quite difficult to get rid of this behavior.



+2


source







All Articles