普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-Jony唐

离屏渲染(二)

作者 Jony唐
2023年3月24日 17:09

Pasted Graphic.png

有哪些操作到导致离屏渲染?

一、 添加光栅化

光栅化是一个缓存机制,如果开启了光栅化,它会将图片以一个bitmap位图的形式,保存起来,当下一次需要时候,CPU直接从缓存里拿出来交给GPU进行处理,这样GPU就不需要进行一些渲染的计算,光栅化就是一个能减少GPU计算的操作,光栅化是一个提高性能的操作。但是,日常使用光栅化的场景非常少,光栅化使用有限制,因为bitmap只能缓存100ms

//光栅化 
- (void)shouldRasterize {
    self.testImageView.layer.shouldRasterize = YES;
}

可以看到开启光栅化会触发离屏渲染:

Pasted Graphic 1.png

二、添加遮罩

- (void)setMask {
    //添加到layer的上层
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(30, 30, self.testImageView.bounds.size.width, self.testImageView.bounds.size.height);
    layer.backgroundColor = [UIColor redColor].CGColor;
    self.testImageView.layer.mask = layer;
}

会触发离屏渲染:

Pasted Graphic 2.png

因为添加遮罩,创建出来的layer会被添加到原本图像的默认layer上面,而屏幕上的每一个像素点是通过多层layerGPU混合计算出来的,多添加了一层layer,就是类似上篇文章讲的,层级变复杂了,这样GPU无法把需要呈现的图像一次绘制完毕,他只能用离屏渲染的方式来处理。

三、添加阴影

//阴影
- (void)setShadows {
    self.testImageView.layer.shadowColor = [UIColor redColor].CGColor;
    self.testImageView.layer.shadowOffset = CGSizeMake(20, 20);
    self.testImageView.layer.shadowOpacity = 0.2;
    self.testImageView.layer.shadowRadius = 5;
    self.testImageView.layer.masksToBounds = NO;
}

会触发离屏渲染:

Pasted Graphic 3.png

这个阴影和遮罩mask是很像,只不过遮罩是添加到layer的上层,而阴影是添加到layer的下层,它的层级也比较复杂,所以也会触发离屏渲染。

四、使用贝塞尔曲线进行优化

//阴影优化
- (void)setShadows2 {
    self.testImageView.layer.shadowColor = [UIColor redColor].CGColor;
    self.testImageView.layer.shadowOpacity = 0.2;
    self.testImageView.layer.shadowRadius = 5;
    self.testImageView.layer.masksToBounds = NO;
    
    //提前指定阴影的路径
    [self.testImageView.layer setShadowPath:[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.testImageView.bounds.size.width + 20, self.testImageView.bounds.size.height + 20)].CGPath];
}

没有离屏渲染:

Pasted Graphic 4.png

阴影是添加在layer的下层,阴影会优先会被渲染,在渲染阴影的时候依赖视图的大小,但视图本身还没有被渲染好,这个时候只能通过离屏渲染进行辅助处理,贝塞尔曲线就是提前指定阴影的路径,这个阴影的渲染就不需要依赖视图本体,这个阴影会被单独地进行渲染,不需要通过离屏渲染辅助合成图像。

五、抗锯齿

//抗锯齿
- (void) setEdgeAnntialiasing {
    CGFloat angle = M_PI / 60.0;
    [self.testImageView.layer setTransform:CATransform3DRotate(self.testImageView.layer.transform, angle, 0.0, 0.0, 1.0)];
    self.testImageView.layer.allowsEdgeAntialiasing = YES;
} 

这个要分情况,在图片Content ModeAspect Fill的模式下:

Pasted Graphic 5.png

会触发离屏渲染:

Pasted Graphic 6.png

如果改为Scale To Fill或者Aspect Fit等不需要进行抗锯齿计算的模式就不会触发离屏渲染:

Pasted Graphic 7.png

因此,icon的图片最好跟控件的比例或者尺寸是一样的,最大可能地减少离屏渲染的可能性。

六、不透明

不透明也要分两种情况:

//不透明
- (void)allowsGroupOpacity {
  self.testImageView.alpha = 0.5;
  self.testImageView.layer.allowsGroupOpacity = YES;
}

1. 如果是自己本身不透明,并不会触发离屏渲染:

Pasted Graphic 8.png

2. 一种还有子视图的情况开启allowsGroupOpacity

- (void)allowGroupOpacity {
  UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
  view.backgroundColor = [UIColor greenColor];
  [self.testImageView addSubview:view];
    
  self.testImageView.alpha = 0.5;
    //allowsGroupOpacity 设置视图的子视图在透明度上是否和俯视图一致
  self.testImageView.layer.allowsGroupOpacity = YES;
}
  • 如果本身还有子视图,父视图不透明度小于1,开启允许组不透明就需要混合计算,这样就会触发离屏渲染:

Pasted Graphic 9.png

  • 如果本身还有子视图,父视图不透明度为1,开启允许组不透明就需要混合计算,这样不会触发离屏渲染:

1__#$!@%!#__Pasted Graphic.png

七、圆角

1. 设置背景颜色和圆角

- (void)setRadius {
    self.testImageView.backgroundColor = [UIColor redColor];
    self.testImageView.layer.cornerRadius = 45;
}

会触发离屏渲染:

1__#$!@%!#__Pasted Graphic 5.png

2. 只设置圆角

不会触发:

1__#$!@%!#__Pasted Graphic 7.png

3. 给子视图标签添加圆角和颜色

- (void)setRadius {
    self.testImageView.backgroundColor = [UIColor redColor];
    self.testImageView.layer.cornerRadius = 40;
    self.testLabel.backgroundColor = [UIColor greenColor];
    self.testLabel.layer.cornerRadius = 10;
}

发现绿色标签不会:

1__#$!@%!#__Pasted Graphic 8.png

4. 如果给label的layer层添加颜色

- (void)setRadius {
    self.testImageView.backgroundColor = [UIColor redColor];
    self.testImageView.layer.cornerRadius = 40;
    self.testLabel.layer.backgroundColor = [UIColor greenColor].CGColor;
    self.testLabel.layer.cornerRadius = 10;
}

会触发离屏渲染:

1__#$!@%!#__Pasted Graphic 9.png

因为labellayer设置背景颜色,它实际是给contents设置颜色

图片设置圆角的时候,实际是设置给borderbackgroundColor设置,不会设置contents,所以图片需要Clips to Bounds

Pasted Graphic 10.png

iOS9之后给视图设置单纯设置圆角不会触发离屏渲染,但是如果同时操作contents + backgroundColor/border就会触发。

八、贝塞尔曲线

//贝塞尔曲线
- (void)setBezier {
    //开始上下文 UIGaphicsBeginImageContextWithOptions(self.testImageView.bounds.size, NO, 0.0);
    [[UIBezierPath bezierPathWithRoundedRect:self.testImageView.bounds cornerRadius:40] addClip];
    [self.testImageView drawRect:self.testImageView.bounds];
    //当前上下文
    self.testImageView.image = UIGraphicsGetImageFromCurrentImageContext();
    // 结束上下文
    UIGraphicsEndImageContext();
}

不会触发离屏渲染:

Pasted Graphic 11.png

九、drawRect使用

-(void)drawRect:(CGRect)rect {
    //特殊的离屏渲染
    //baking store -- bitmap
}

视图本身就有一块画布,drawRect会重新增加一块画布,会重新生成baking store,增加内存消耗,虽然检测不到,但也会触发离屏渲染,是特殊的离屏渲染。

启动优化clang插桩(一)

作者 Jony唐
2023年3月21日 20:31

启动优化clang插桩(一)

一、了解Clang

首先到Clang地址:Clang Documentation Pasted Graphic.pngPCs指的是CPU的寄存器,用来存储将要执行的下一条指令的地址,Tracing PCs就是跟踪CPU将要执行的代码。

二、如何使用

网页下拉有个Example Pasted Graphic 1.png 使用之前要在工程添加标记: Pasted Graphic 2.png

编译器就会在每一行代码的边缘插入这一段函数:__sanitizer_cov_trace_pc_guard(&guard_variable)

打开实例demo,在Build Settings 搜索 Other c Flag 填入 -fsanitize-coverage=trace-pc-guard

1__#$!@%!#__Pasted Graphic 1.png

项目会报未定义符号的错:

Pasted Graphic 7.png

这就需要去定义这两个符号,先把这两个函数复制过来:

Pasted Graphic 5.png 先把代码复制进ViewController

extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}
// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
  // If you set *guard to 0 this code will not be called again for this edge.
  // Now you can get the PC and do whatever you want:
  //   store it somewhere or symbolize it and print right away.
  // The values of `*guard` are as you set them in
  // __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
  // and use them to dereference an array or a bit vector.
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  // This function is a part of the sanitizer run-time.
  // To use it, link with AddressSanitizer or other sanitizer.
  __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

把头文件也粘贴进来:

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

两个方法里面都有extern “C”extern “C”的主要作用是为了能够正确实现C++去调用其他C语言的代码,加上extern “C”就会指示作用域内的代码按照C语言区编译,而不是C++,这个extern “C”在OC项目里没什么用,直接删除

此时还会包一个错误:

Pasted Graphic 8.png

这个__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));函数没有什么作用,直接删除即可。

三、代码调试

cmd + r运行,此时终端会打印一些信息:

Pasted Graphic 9.png

删除两个函数里面的注释,先注释第二个的内容,然后运行

INIT: 0x1025c5478 0x1025c54f0

这是运行打印得到的地址,就是函数(uint32_t *start, uint32_t *stop)startstop两个指针的地址

stop存储的就是我们工程里面符号的个数

for (uint32_t *x = start; x < stop; x++)
        *x = ++N;

看一下这个for循环,start会先复制给*xx++就是内存平移,按照uint32_t的大小去平移,而uint32_t的定义是typedef unsigned int uint32_t; 是无符号整型,占4个字节,所以每次按4个字节平移。

startstop里面存的是什么,打断点调试:

Pasted Graphic 10.png

先看start:

INIT: 0x1042a5278 0x1042a52e0
(lldb) x 0x1042a5278
0x1042a5278: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x1042a5288: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00  ................
(lldb)

由于uint32_t4个字节来存储发现start就是 0 1 2 3 4…,再看stop,由于stop的已经是结束位置,读取的数据是在startstop之间的数据,所以需要向前平移4个字节得到其真实数据。

(lldb) x (0x1042a52e0-4)
0x1042a52dc: 1a 00 00 00 00 00 00 00 00 00 00 00 fe f1 29 04  ..............).
0x1042a52ec: 01 00 00 00 00 00 00 00 00 00 00 00 90 40 2a 04  .............@*.
(lldb)

可以得到1a 就是26,也可以循环外面打印结果: Pasted Graphic 11.png 可以得到:

TraceDemo[16814:301325] 26

也是26个符号。

四、测试验证方法

可以验证一下,添加一个函数:

void test(void) {
    NSLog(@"%s",__func__);
}

符号变成27

TraceDemo[16911:304537] 27

再添加一个block

void (^block) (void) = ^{
    NSLog(@"%s",__func__);
};

符号变成28

TraceDemo[16933:305465] 28

添加一个数据类型属性:

@property (nonatomic ,assign) int age;

由于系统自动生成getter、setter方法,符号变成30

TraceDemo[16975:306816] 30

添加一个对象属性:

@property (nonatomic ,copy) NSString *str;

符号变成33

TraceDemo[17041:308780] 33

对象属性由于ARC,系统自动除了生成getter、setter方法外还生成了cxx_destruct()析构函数

添加一个方法:

- (void)test{
}

符号变成34

TraceDemo[17114:311256] 34

在其他类AppDelegate类中添加一个属性:

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) NSString *name;
@end

符号变成37:

TraceDemo[17266:316294] 37

符号变成37

结论

这就说明了通过这个方法整个项目里的符号,它都能捕获到。

❌
❌