为啥 renderInContext 比绘制到屏幕慢得多?

Posted

技术标签:

【中文标题】为啥 renderInContext 比绘制到屏幕慢得多?【英文标题】:Why is renderInContext so much slower than drawing to the screen?为什么 renderInContext 比绘制到屏幕慢得多? 【发布时间】:2012-04-10 18:03:53 【问题描述】:

我正在渲染一个相当大的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】:

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

【讨论】:

不要使用 CATiledLayer。它的实现非常糟糕(ios 5 + 6)并且在所有非平凡的情况下都会扼杀性能。它似乎是来自 Apple 的“概念证明”,而不是他们期望人们实际使用的类。瓦片没有以理智的方式加载-相反,它们具有(设计不佳的)自定义缓存机制,并且……它们忽略了硬件自己的基于瓦片的缓存。叹息 你有什么建议? 编写自己的代码。 Apple 目前 - 2013 年 - 没有为这些问题提供任何解决方案。

以上是关于为啥 renderInContext 比绘制到屏幕慢得多?的主要内容,如果未能解决你的问题,请参考以下文章

具有指定大小的 renderInContext 图像绘制

VBO 比绘制图元的过时方法慢 - 为啥?

使用 renderInContext 将视图层保存到图像

为啥使用 CGContextDrawImage 旋转图像比绘制旋转的 UIImage 慢得多?

为啥在屏幕上绘制图像之前不会出现内存不足的问题?

为啥在 GLFW 窗口中使用此代码在我的屏幕上没有绘制立方体?