オフスクリーンレンダリングを行う方法

序文

同社は最近UIリストを開発し、開発してからセルのスタイルが実現したのですが、オフスクリーンレンダリングができるといつも思っていたので、そう感じたらチェックしたいと思いました。オフスクリーンレンダリングはありましたか?レンダリング、それをどのように引き起こすことができますか、そして良い解決策は何ですか?

最初にデザインを見てみましょう。

20211210154826.jpg

角が丸い白い背景。その上に画像があり、画像の左隅と右上隅も丸みを帯びており、境界線や影はありません。

どのように書きますか

CALayerとUIViewの違い

  • CALayer:から継承され 、画像のレンダリングを担当し、 フレームワークNSObjectに属し ます。QuartzCore
  • UIView: フレームワークUIResponderに基づいて、主にイベント応答を担当する から継承されUIKit ます。

両者が分かれて協力していることは明らかです。多くの人は、UIViewにも背景、フレーム、バインドなどがあると考えるでしょう。これらのプロパティの本質は、CALayerのカプセル化でもあります。

ClipsToBoundsとmasksToboundsの違い

  • ClipsToBounds:はクラスViewのプロパティです。yesに設定すると、親ビューを超える部分は表示されません。

  • masksToBounds:はクラスCALayerのプロパティです。yesに設定すると、親のViewレイヤーを超える部分は表示されません。

それらはすべて同じ効果を達成し、本質は同じであると言えます。

オフスクリーンレンダリング

オフスクリーンレンダリングとは

通常、ディスプレイにコンテンツを表示する場合は、画面と同じ量のピクセルデータFramebufferを。GPUはレンダリングされたコンテンツをFramebufferニードルバッファーに入れ続け、ディスプレイはFramebufferそれを続けて表示します。 display.content

いくつかの制限により、GPUはレンダリング結果をFramebuffer一番上に書き込むことができません。レンダリング操作のために現在の画面バッファーの外側に新しいバッファーを開きOffscreen Buffer、事前にレンダリングされたコンテンツを一番上に置き、それが終わったら一番上にOffscreen Buffer書き込む必要があります。Framebufferこれはオフスクリーンレンダリングです。

20211210163357.jpg

画家のアルゴリズム

image.png

画家算法是图层由远到近,一层一层绘制的。同样的道理也会应用到我们GPU渲染图层,也是一层一层往画布上输出,上层的sublayer会覆盖下层的sublayer,相当于按次序输出到framebuffer,然后按照次序绘制到屏幕,当绘制完一层,就会将该层从帧缓存区中移除,从而节约空间提交效率。

但是有些场景,GPU一层一层渲染,渲染完成后放到framebuffer,如果说你想改变在这一层的layer像素数据,这时候已经不能改变了,因为它在渲染中被永久覆盖了。那怎么办?

那只能令开辟一块内存作为临时中转区来完成复杂的修改/裁剪等操作,然后再切换到framebuffer上。

离屏渲染优缺点

优点

  • 处于效率目的,对于多次出现屏幕的视图,可以将内容提前渲染保存在 Offscreen Buffer 中,达到复用的目的。

缺点

  • 离屏渲染需要额外开辟一个新的缓存区,这会增加存储空间,大量的离屏渲染可能会造成内存过大的压力,空间大小为屏幕总像素的2.5倍。超出就无法使用。

  • 在渲染过程,我们需要进行2次上下文切换,先切换到屏幕外环境进行渲染,然后再切换到当前屏幕是很消耗性能的,而且如果最终存入到framebuffer上,超过16.67ms,会产生掉帧现象,造成卡顿。

离屏渲染场景

  1. 光栅化,shouldRasterize,一旦设置为true,就会把layer的渲染结果包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。主旨在于降低性能损失,但总是至少会触发一次离屏渲染。

  2. mask,遮罩作用。

  3. opacity,设置layer的透明度不为1。

  4. shadow,投影作用。

  5. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)。

  6. UIBlurEffectView,高斯模糊,系统自动触发,用于保存中间状态。

  7. masksToBounds,裁剪的一些情况。

圆角 + 裁剪

看了那么多概念,那我们就来分析一下关于圆角+裁剪的情况。先给一个结论:并不是所有圆角+裁剪都会产生离屏渲染。

我们先打开debug -> Color Off-screen Rendered,如果有黄色区域显示,就证明是产生了离屏渲染。

我们先看一下官方给的圆角离屏渲染的图片。

image.png

也就是说圆角裁剪的时候,backgroundColor, contents, borderWidth, borderColor,如果都设置了这些属性就会产生离屏渲染。

圆角+裁剪+边框+背景颜色

- (void)drawView
{
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.systemPinkColor;

    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210173441.jpg

结果是没有离屏渲染。这是很明显的,因为没有需要裁剪处理的内容,所以masksToBounds设置为YES或者NO都没有影响。

圆角+裁剪+contents+边框或背景色

- (void)drawView2 {

    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.grayColor;

    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;

    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210174012.jpg

从效果图上看是离屏渲染了,全部都占有了,还不有问题对吧。

圆角+裁剪+contents

- (void)drawView3 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"destination_head_bg"].CGImage;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.layer.masksToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210174257.jpg

从效果图上看,是没有离屏渲染的,这里是重点,我们只设置了contents,没有设置背景颜色或者边框,就不会产生离屏渲染。因为我们就只有这一层,我们单层情况下,直接裁剪就行,不用放到Offscreen Buffer上进行渲染。

圆角+裁剪+有内容子视图+背景色或边框

- (void)drawView4 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];

    // 设置背景色
    view1.backgroundColor = UIColor.grayColor;

    // 设置圆角
    view1.layer.cornerRadius = 100;
    
    // 设置裁剪
    view1.layer.masksToBounds = YES;

    // 子视图
    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100.0, 100.0)];

    // 设置背景色
    view2.backgroundColor = [UIColor blueColor];

    [view1 addSubview:view2];

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210175446.jpg

从效果图上是有离屏渲染的,子视图不在裁剪的范围的,但设了背景色就产生了离屏渲染。如果去掉view1的背景色,那就不会离屏渲染了。

圆角+裁剪+子视图在裁剪区域

- (void)drawView5 {
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 375.0, 375.0)];

    // 设置圆角
    view1.layer.cornerRadius = 16;

    // 设置裁剪
    view1.layer.masksToBounds = YES;

    UIImageView * imageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"destination_head_bg"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
    imageView.frame = CGRectMake(0, 0, 375.0, 200.0);

    [view1 addSubview:imageView];

    view1.center = self.view.center;
    [self.view addSubview:view1];
}
复制代码

20211210175939.jpg

从效果图上看还是离屏渲染了,本来以为对view1不设置背景颜色,能解决我在前言提出的问题,没想到还是离屏渲染了。

结论

  • 丸みを帯びた角とトリミングを設定した、ビューにコンテンツ(画像、描画コンテンツ、画像情報を含むサブビュー)に加えて、背景色または境界線が必要な場合、オフスクリーンレンダリングが発生します

  • 丸みを帯びた角とトリミングを設定し、さらにサブビューをトリミング領域に配置すると、画面外にもレンダリングされます

  • 丸みを帯びた角とトリミングのみコンテンツは画面外に表示されません。古典的な例は【button setImage】、一致する必要があるだけで、画面外にレンダリングされることはありませんbutton.imageView.layer.cornerRadiusbutton.imageView.clipsToBounds

解決

  1. デザイナーをお探しですか?角が丸くて影のあるカードなど、デザイナーにうまくデザインさせましょう。

  2. UIBezierPath + CAShapeLayer?左上と右上を自分で描きますか?効果はまだオフスクリーンレンダリングであり、パフォーマンスが向上する可能性があります。

  3. マスクを入手し、丸みを帯びた角をシミュレートしますか?

皆さん、序文の問題のために、オフスクリーンレンダリングなしでレンダリングすることはまだ不可能です。オンラインヘルプが大物に与えられます、ありがとう!

おすすめ

転載: juejin.im/post/7040015747510173727