UIView在Retina显示屏上显示drawRect / update图像性能问题

时间:2014-09-17 21:08:43

标签: ios performance uiimageview core-graphics retina-display

问题简短:

LooksLike drawRect本身(甚至是空的)会导致显着的性能瓶颈,具体取决于设备的分辨率 - 更大的屏幕是更糟糕的事情。 有没有办法加快重新绘制视图的内容?

更详细信息:

我在iOS上创建一个小型绘图应用程序 - 用户将手指移到显示屏上以绘制线条。

这背后的想法非常简单 - 当用户移动他的手指时touchesMoved将更改累积到屏幕外缓冲区图像中,并使视图无效以将屏幕外缓冲区与视图内容合并。

简单的代码段可能如下所示:

@interface CanvasView : UIView
...
end;


@implementation CanvasView{
    UIImage *canvasContentImage;
    UIImage *bufferImage
    CGContextRef drawingContext;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    // prepare drawing to start
    UIGraphicsBeginImageContext(canvasSize);
    drawingContext = UIGraphicsGetCurrentContext();
    ...

}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    // draw to bufferImage
    CGContextMoveToPoint(drawingContext, prevPoint.x, prevPoint.y);
    CGContextAddLineToPoint(drawingContext, point.x, point.y);
    CGContextStrokePath(drawingContext);
    ...
    [self setNeedDisplay];
}

-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    //finish drawing
    UIGraphicsEndImageContext();
    //merge canvasContentImage with bufferImage
    ...
}

-(void)drawRect:(CGRect)rect{
    // draw bufferImage - merge it with current view's content

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, self.bounds, canvasContentImage.CGImage);
    CGContextDrawImage(context, imageRect, bufferImage.CGImage);
    ...

}

我还实现了一个小助手类来计算fps率。

因此上述方法在非视网膜屏幕上的效果相当好,产生近60fps。然而fps率在视网膜屏幕上显着下降。例如,在iPad Retina上它大约是15-20 fps,这太慢了。

我认为第一个明显的原因是setNeedsDisplay导致重绘整个屏幕,这是一个巨大的资源浪费。所以我转移到setNeedsDisplayInRect只更新一个脏区域。令人惊讶的是,它没有改变任何与性能相关的东西(至少根据测量和视觉上没有任何明显的变化)。

所以我开始尝试不同的方法来找出瓶颈。当我评论出所有的绘图逻辑时,fps率仍然保持在15-20 - 看起来问题不在于绘制逻辑。 最后,当我完全注释掉drawRect方法时,fps上升到60.不是说我只删除了实现,而是删除了声明。我不确定我的术语所以结果如下:

//    -(void)drawRect:(CGRect)rect{
//        // draw bufferImage - merge it with current view's content
//        ...
//    }

当我将所有绘图代码从drawRect方法移动到touchMoved方法时,更有趣的是它不会影响性能,但是相同数量的绘图/处理逻辑仍然在比较使用drawRect方法的版本 - 甚至每次更新整个视图仍然给我60fps。 一个问题是如果没有drawRect,我就无法看到这些变化。

所以,我已经了解了预生成drawRect方法的警告:

  

"仅覆盖drawRect:如果执行自定义绘图。一个空的   实施会对动画期间的表现产生负面影响。"

我的猜测是,每次触发自定义drawRect时,系统都会创建并销毁图形上下文,导致"反过来影响性能"

所以问题是:

  1. 有没有办法加快drawRect次来电,例如让系统重用资源从呼叫到drawRect或其他什么?

  2. 如果这是一个死胡同,还有哪些其他可用于更新视图内容的方法?目前不能选择转向OpenGL,因为已经实现了很多代码/逻辑,并且需要花费很多精力来移植它。

  3. 我很乐意提供所需的任何其他信息。 并提前感谢任何建议!

    修改

    经过一些调查和实验后,我开始使用UIImageView及其图像属性动态更新视图内容。它提供了小的性能改进(绘图在19-22 fps时更稳定)。然而它仍然远远没有目标50-60fps。 有人认为我已经注意到,仅更新屏幕外缓冲区图像的脏部分确实有意义 - 不强制视图的内容更新,重绘屏幕外缓冲区的纯逻辑提供大约60 fps。 但是,一旦我尝试将更新后的图像附加到UIImageView.image属性来更新它的内容,fps就会降到19-22。这是合理的,因为分配属性会强制整个图像在视图侧重绘。

    所以问题仍然存在 - 有没有办法只更新显示内容的视图的指定部分(UIImageView')

1 个答案:

答案 0 :(得分:1)

花了好几天后,我意外地(至少对自己来说)结果。 我能够在视网膜iPad上达到30fps,这是现在可接受的结果。

对我有用的诀窍是:

  1. 子类UIImageView
  2. 使用UIImageView.image属性更新内容 - 与使用setNeedsDisplay / setNeedsDisplayInRect方法的普通UIView相比,它提供了更好的结果。
  3. 使用[self performSelector:@selector(setImage:) withObject:img afterDelay:0];而不是UIImageView.image = img将更新的图像设置为UIImageView。
  4. 最后一点对我来说是一种咒语,但是它提供了最小的必要帧速率(即使原始图像每帧都重新绘制,不仅是脏区域)。

    我的猜测为什么在我的情况下,为什么performSelector有助于获得更新视图的fps,因为视图队列上的调度setImage可以优化触摸事件处理期间可能发生的内部空闲。 这只是我的猜测,如果有人能提供相关解释,我很乐意在这里发布或接受答案。