何时需要进行离屏渲染来显示视图(或图层)?

57

你好
这个周末我开始观看2011年WWDC的视频,发现其中一些关于iOS的话题非常有趣。 我最喜欢的是有关性能和图形方面的内容,但我发现其中两个似乎相互矛盾。 当然,我还有一些问题没有得到解答。 我所说的会话是Understanding UIKit Rendering -121 和Polishing your app -105.
不幸的是,2011年的示例代码仍然无法下载,因此很难获得整体视图。 在一个会话中,他们解释说,在可滚动视图等可视化过程中,大多数时间应避免屏幕外渲染。 他们通过几乎在-drawRect方法内部绘制所有内容来修复示例代码中的性能问题。 在另一个会话中,性能问题(在表视图上)似乎是由于表单元格的-drawRect方法中有太多的代码引起的。
首先,我不清楚系统何时需要屏幕外渲染。 我在视频中看到了一些quartz函数,例如:cornerRadious,shadowOffset,shadowColor需要它,但是否存在一般规则呢? 其次,我不知道是否理解正确,但似乎当没有屏幕外渲染时,添加层或视图是正确的方法。 希望有人能为此带来启示。
谢谢,
安德烈亚


请问您能否添加视频链接? - taskinoor
没有直接链接。您应该是注册的开发者。链接 - Andrea
当我看到这些视频时,我也在想同样的问题,不知道当视图被绘制到屏幕外时是否有官方规定,以及如何禁止它。 - myell0w
3个回答

158

我认为没有规定写在任何地方,但希望这可以帮助解决问题:

首先,让我们澄清一些定义。我认为屏幕外渲染与屏幕内渲染并不是大多数情况下最重要的问题,因为屏幕外渲染可以和屏幕内渲染一样快。主要问题是渲染是否在硬件或软件中完成。

使用图层和视图之间的实际区别也很小。视图只是CALayer的一个薄包装器,并且大部分时间它们不会引入显着的性能损失。如果想要拥有由CAShapeLayer或CATileLayer等支持的视图,则可以使用+layerClass方法覆盖所使用的层类型。

通常,在iOS上,像素效果和Quartz / Core Graphics绘图都没有硬件加速,而其他大多数事物都有。

以下内容没有硬件加速,这意味着它们需要通过软件(屏幕外)完成:

  1. 在drawRect方法中完成的任何操作。如果您的视图有drawRect方法,即使是空的,绘制也不是在硬件中完成,会产生性能损失。

  2. 任何shouldRasterize属性设置为YES的图层。

  3. 具有蒙版或投影的任何图层。

  4. 文本(任何类型,包括UILabels、CATextLayers、Core Text等)。

  5. 您自己完成的任何绘图(无论是在屏幕上还是屏幕外)都使用CGContext。

其他大多数事物都有硬件加速,因此它们更快。但这可能并不是您想象的那样。

与硬件加速绘图相比,上述任何一种绘图都很慢,但它们不一定会减慢您的应用程序,因为它们不需要每帧都发生。例如,在视图上绘制投影首次很慢,但绘制完后会被缓存,并且仅在视图更改大小或形状时重新绘制。

同样的,对于栅格化视图或带有自定义drawRect的视图,该视图通常不会在每一帧中重新绘制,而是绘制一次并缓存起来,因此在视图首次设置后性能不会变差,除非边界发生更改或者调用了setNeedsDisplay。
为了获得良好的性能,关键是避免在每一帧都需要更改的视图中使用软件绘图。例如,如果您需要一个动态矢量形状,则使用CAShapeLayer或OpenGL比使用drawRect和Core Graphics获得更好的性能。但是,如果您只需绘制一次形状,并且不需要更改它,那么这并不会产生太大的影响。
同样地,不要将投影放在动画视图上,因为它会降低帧率。但是,对于从一帧到另一帧不会更改的视图上的阴影效果,影响不会太大。
另一个要注意的问题是不要减慢视图设置时间。例如,假设您有一个文本页面,所有文本都带有投影;由于文本和阴影都需要在软件中渲染,因此初始绘制会花费很长时间,但一旦绘制完成,它就会变得很快。因此,您需要在应用程序加载时提前设置这个视图,并在内存中保留一份副本,以便用户不必等待太久才能看到视图首次出现在屏幕上。
这可能是WWDC视频中表面上矛盾的原因。对于大型、复杂的视图,如果它们不会每一帧都进行更改,则在软件中绘制它们一次(之后将缓存它们,无需重新绘制)将比每一帧都让硬件重新合成它们具有更好的性能,尽管第一次绘制会更慢。
但是对于必须经常重绘的视图,例如表格单元格(单元格会被回收,因此每次一个单元格从屏幕滚动到另一边并作为不同行再次滚回来时都必须重绘),使用软件绘图可能会大大降低性能。

好的答案。已设置赏金。谢谢。 - n00bProgrammer
shouldRasterize的默认值是NO。这是否意味着所有这些图层既不会被绘制到屏幕外,也不会被缓存? - dev gr
@devgr 不一定需要。某些情况下需要离屏渲染,例如动态阴影和遮罩。即使没有将 shouldRasterize 设置为 true,这也需要离屏渲染。此外,据我所知,图层在可以时会被缓存,特别是自 iOS 7 引入实时模糊和其他视觉效果以来。核心动画会在可能的情况下积极缓存图层内容(可能是模糊变体)。shouldRasterize 只是强制在合成之前对图层树进行光栅化,这可能会或可能不会提高性能。 - CIFilter

35

离屏渲染是iOS渲染中最不清楚的话题之一。当苹果的UIKit工程师提到离屏渲染时,它具有非常具体的含义,而许多第三方iOS开发博客都弄错了。

当您重写“drawRect:”时,您是通过CPU绘制,并且输出位图。位图被打包并发送到生活在iOS中的单独进程,即渲染服务器。理想情况下,渲染服务器只会在屏幕上显示数据。

如果您在CALayer上调整属性,例如打开投影阴影,则GPU将执行附加绘图。这个附加工作是UIKit工程师所说的“离屏渲染”。这始终使用硬件执行。

离屏绘制的问题不一定在于绘制本身。离屏传递需要上下文切换,因为GPU切换其绘图目标。在此切换期间,GPU处于空闲状态。

虽然我不知道触发离屏传递的所有属性列表,但您可以通过核心动画工具的“颜色离屏渲染层”切换来诊断此问题。我认为除alpha以外的任何属性都是通过离屏传递执行的。

在早期的iOS硬件中,可以合理地说“在drawRect中完成所有工作”。现在GPU更好了,而且UIKit具有shouldRasterize等功能。今天,它是在drawRect中花费的时间、离屏传递的数量和混合量之间的平衡。有关完整信息,请观看2014 WWDC会话419,“iOS应用程序的高级图形和动画”。

尽管如此,了解幕后发生的情况并将其牢记在心,以便不要做出任何疯狂的事情是很好的,但您应该从最简单的解决方案开始。然后,在您支持的最慢硬件上进行测试。如果没有达到60FPS,则使用Instruments测量数据并找出原因。存在一些可能的瓶颈,如果您没有使用数据来诊断问题,那么您只是在猜测。


18

离屏渲染 / CPU 渲染

图形性能的最大瓶颈是离屏渲染和混合 - 它们可能会在每个帧动画中发生,并导致滚动不流畅。

离屏渲染(软件渲染)发生在需要先在软件中进行绘制(离屏)后才能交给 GPU 处理的情况。硬件无法处理文本渲染以及具有遮罩和阴影的高级合成。

以下情况将触发离屏渲染:

  • 任何带有遮罩(layer.mask)的图层

  • 任何带有 layer.masksToBounds / view.clipsToBounds 设置为 true 的图层

  • 任何带有 layer.allowsGroupOpacity 设置为 YES 且 layer.opacity 小于 1.0 的图层
    什么时候需要视图(或图层)进行离屏渲染?

  • 任何带有阴影(layer.shadow*)的图层。
    如何修复:https://markpospesel.wordpress.com/tag/performance/

  • 任何带有 layer.shouldRasterize 设置为 true 的图层

  • 任何带有 layer.cornerRadiuslayer.edgeAntialiasingMasklayer.allowsEdgeAntialiasing 的图层

  • 任何带有 layer.borderWithlayer.borderColor 的图层?
    缺少参考 / 证明

  • 文本(任何类型,包括 UILabelCATextLayerCore Text 等)。

  • 大部分使用CGContext进行绘制的工作都在drawRect:中完成。即使是空的实现也会在屏幕外呈现。


  • 本文涵盖了混合和其他影响性能的内容:iOS中触发离屏渲染、混合和layoutSubviews的因素是什么?


    clipsToBounds/masksToBounds/cornerRadius不再导致离屏渲染。 - Legonaftik

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接