为什么renderInContext比绘制到屏幕要慢得多?

时间:2012-04-10 18:03:53

标签: ios core-animation calayer

我正在渲染一个非常大的CAShapeLayer。该图层是完全静态的,但它包含在UIScrollView中,因此它可以移动并进行缩放 - 基本上,它必须时不时地重新绘制。为了提高此滚动的帧速率,我在图层上设置shouldRasterize = YES,这非常有效。因为我永远不会改变图层的任何属性,所以它永远不会有光栅化错过,我得到一个稳定的60 fps。四周高五,对吧?

直到图层变得更大。最终 - 并且不需要很长时间 - 光栅化图像太大而无法处理GPU。根据我的控制台<Notice>: CoreAnimation: surface 2560 x 4288 is too large,它只是不在屏幕上绘制任何内容。我并没有真正责怪它 - 2560 x 4288非常大 - 但在我在设备控制台中注意到这一点之前,我花了一些时间挠头。

现在,我的问题是:我如何解决这个限制?如何栅格化一个非常大的图层?

显而易见的解决方案似乎是将图层分成多个子图层,例如每个图像一个子图层,并且每个子图层独立栅格化。是否有捷径可寻?我可以创建一个从另一个图层渲染矩形区域的新图层吗?或者我应该探索一些其他解决方案吗?

修改

创建平铺复合体似乎具有非常糟糕的性能,因为每次进入屏幕时都会对图层进行重新栅格化,从而创建非常不稳定的滚动体验。有没有办法缓存那些光栅化?或者这完全是错误的做法?

修改

好的,这是我当前的解决方案:将图层渲染一次到CGImageRef。使用该图像中的子矩形创建多个切片图层,并将其实际放在屏幕上。

- (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;
}

这很有用......有点儿。一方面,我在视网膜iPad上进行60fps平移和缩放1980 x 3330层。另一方面,启动需要20秒!因此,虽然这个解决方案解决了我的原始问题,但它给了我一个新的问题:如何更快地生成切片?

从字面上看,所有时间都用在[sourceLayer renderInContext:tileContext];电话中。这对我来说似乎很奇怪,因为如果我直接添加该层,我可以每秒渲染大约40次,根据Core Animation Instrument。是否有可能创建我自己的图像上下文导致它不使用GPU或什么?

1 个答案:

答案 0 :(得分:2)

将图层分解为图块是唯一的解决方案。但是,您可以通过许多不同的方式实现它。我建议手动完成(自己创建图层和子图层),但很多人建议使用CATiledLayer http://www.mlsite.net/blog/?p=1857,这是通常实现地图的方式 - 使用这种方法可以轻松进行缩放和旋转。 CATiledLayer的图块按照按需加载(绘制),就在它们被放到屏幕上之后。这意味着在完全绘制图块之前有一个短暂的延迟(闪烁),AFAIK很难摆脱这种行为。