视图-更新机制

[toc]

视图-更新机制

系统有基本稳定的刷新频率,在layer内容改变的时候,把这个layer做个需要刷新的标记,即是setNeedsDisplay

然后每次刷新时,把上次刷新之后被标记的layer一次性全部提交给图形系统,所以这里还有一个东西,就是事务(CATransaction)

其他类似举例:

1
2
3
4
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: tableData.count-2, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.insertRows(at: [IndexPath(row: tableData.count-1, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()

beginUpdatesendUpdates 方法的作用是,让这两条语句之间的对 tableView 的操作( insert/delete)不立即执行,而是先聚合起来,然后同时更新 UI

1、刷新频率

1.1、显示器的刷新率 VS 显卡渲染的帧率

1、显示器的刷新率为 50Hz,显卡渲染的帧率是 200FPS

即显示器20ms显示一帧,显卡5ms渲染一帧。

则20ms里,显卡渲染了4帧数据,但显示器只能显示了一次,该画面由4帧数据组成,造成撕裂。

所以需要压制显卡的渲染速率,使显卡的帧缓冲区切换行为与显示器的帧绘制保持同步。垂直同步(Vertical Synchronization, VSync)即是为了处理此。

好文推荐: 扫描,撕裂和垂直同步 - VSync 技术实现

1.1.1、显示数据的提供来源:帧缓冲区

通常,显示器是一台独立工作的设备,状态与显卡无关,会以恒定不变的频率从某个「池」里面读取画面,以保证稳定的图像输出。与此同时,显卡也会往这个「池」里写画面,以供显示器读取。这个「池」叫「Framebuffer(帧缓冲区)」。

一张显卡通常有 2 个帧缓冲区:主/副(Primary/Secondary)缓冲区(也称前/后缓冲区(Front/Back)),由数据选择器(Multiplexer)选择连接到显示器的缓冲区。连接到显示器的缓冲区总是主缓冲区,显示器从中读取图像内容;未连接到显示器的缓冲区总是副缓冲区,显卡向其中写入渲染好的内容。

显卡总是会尝试以最快的速度渲染内容,每完成一帧渲染即切换主/副缓冲区,与显示器的工作状态完全无关。

image-20240905163331025

当渲染完成后,副缓冲区的内容会与主缓冲区的内容进行交换,这个过程是双缓冲(Double Buffering)技术的一部分,用于避免屏幕撕裂和闪烁,同时提高图像渲染的效率。下面详细解释这个过程:

  1. 主缓冲区(Front Buffer):这是当前正在屏幕上显示的图像所在的缓冲区。用户看到的所有内容都存储在这里,显示器会不断读取这个缓冲区的内容来显示图像。
  2. 副缓冲区(Back Buffer):这是显卡用来准备下一帧图像的缓冲区。当一帧图像显示在屏幕上时,显卡可以在副缓冲区中渲染下一帧图像,而不会影响当前显示的内容。
  3. 渲染过程:显卡开始在副缓冲区中渲染新的一帧图像。这个过程可能包括执行复杂的图形计算,如光照计算、纹理映射、深度测试等。
  4. 缓冲区交换(Buffer Swap):一旦副缓冲区中的新帧渲染完成,显卡会执行一个缓冲区交换操作。这个操作会将副缓冲区的内容复制到主缓冲区,这样显示器就可以开始显示新的一帧图像,而副缓冲区则准备好接受下一帧的渲染数据。
  5. 避免屏幕撕裂:由于显示器是逐行刷新的,如果在显示器刷新过程中显卡正在渲染新的帧,就可能出现屏幕撕裂现象。双缓冲技术通过在渲染完成后才进行缓冲区交换,确保了显示器在任何时候都不会读取到半成品的帧,从而避免了屏幕撕裂。
  6. 提高效率:在双缓冲机制下,显卡可以在一帧显示的同时准备下一帧,这样可以更有效地利用显卡资源,提高渲染效率。
  7. 垂直同步(V-Sync):为了进一步提高图像质量和减少撕裂,有时会使用垂直同步技术。垂直同步会同步显卡的渲染速度和显示器的刷新率,确保缓冲区交换发生在显示器刷新周期的合适时刻。

通过这种方式,双缓冲技术能够在不牺牲渲染效率的情况下,提供流畅且无撕裂的图像显示。

1.2、垂直同步(Vertical synchronization)

V-Sync 的主要作用就是保证只有在帧缓冲区中的图像被渲染之后,后备缓冲区中的内容才可以被拷贝到帧缓冲区中

理想情况下每次 V-Sync 发生时,CPU 以及 GPU 都已经完成了对图像的处理以及绘制,显示器可以直接拿到缓冲区中的帧。即 V-Sync 会按这种方式工作:

image-20240905184838586

但是,如果 CPU 或者 GPU 的处理需要的时间较长,就会发生掉帧的问题,即在 V-Sync 信号发出时,CPU 和 GPU 并没有准备好需要渲染的帧,显示器就会继续使用当前帧。

image-20240905184900129

其实到这里关于屏幕渲染的内容就已经差不多结束了,根据 V-Sync 的原理,优化应用性能、提高 App 的 FPS 就可以从两个方面来入手,优化 CPU 以及 GPU 的处理时间。

iOS 的显示系统是由 VSync 信号驱动的,VSync 信号由硬件时钟生成,每秒钟发出 60 次(这个值取决设备硬件,比如 iPhone 真机上通常是 59.97)。

RunLoop是一个事件循环对象,用于管理线程的事件处理。每个线程都有一个与之对应的RunLoop对象,主线程的RunLoop在应用程序启动时自动创建并运行,而子线程的RunLoop需要手动创建和维护。RunLoop的作用是让线程能够随时处理事件而不退出,它通过循环检查输入源(如Timer、Source等)和定时源,等待接收事件,当没有事件时让线程休眠以节省资源。

Core Animation(CA)是 iOS 中的动画和图形渲染引擎,它在 RunLoop 中注册了 Observer 来监听特定的事件,以便在适当的时机进行渲染操作。这些 Observer 可以监听多种 RunLoop 的事件,包括:

  1. BeforeWaiting:RunLoop 即将进入休眠状态之前,此时没有待处理的事件,系统可能在等待新的输入事件或定时器事件。Core Animation 会在这个时候进行渲染操作,准备下一帧的显示内容。
  2. Exit:RunLoop 即将退出时,这通常意味着线程将结束运行。在这个阶段,Core Animation 可能会进行一些清理工作。

手机硬件时钟生成Vsync信号

=> 图形服务接收到 VSync 信号后,会通过 IPC 通知到 App 内。

=> app收到当VSync信号到达时,应用程序的主线程开始处理显示内容,如视图创建、布局计算等,然后将内容提交给GPU进行渲染。渲染完成后,GPU将结果提交到帧缓冲区,等待下一个VSync信号到来时显示到屏幕上。

=> Runloop接收到时钟信号(App 的 Runloop 在启动后会注册对应的 CFRunLoopSource 通过 mach_port 接收传过来的时钟信号通知,随后 Source 的回调会驱动整个 App 的动画与显示。

当VSync信号到来时,系统图形服务会通过CADisplayLink等机制通知应用程序,

离屏渲染

在iOS设备上,GPU渲染通常发生在帧缓冲区中,这是GPU用来临时存储即将显示到屏幕上的像素数据的区域。帧缓冲区通常采用双缓冲机制,即存在一个前台缓冲区和一个后台缓冲区。前台缓冲区是当前显示在屏幕上的帧,而后台缓冲区是GPU正在渲染的下一帧。

离屏渲染(Off-Screen Rendering)是当GPU无法直接在帧缓冲区中渲染某些效果时所采用的一种技术。以下是一些可以在帧缓冲区处理的效果,以及一些需要离屏渲染的效果:

可以在帧缓冲区处理的效果:

  1. 简单的颜色填充和边框绘制:这些可以直接在GPU的帧缓冲区中进行,因为它们不涉及复杂的像素操作。
  2. 使用contents属性设置的图像:如果图层的内容是一个简单的图像,且没有复杂的变换或混合,这些可以直接在帧缓冲区中渲染。

需要离屏渲染的效果:

  1. 圆角(Rounded Corners):当为UIView或其子类设置cornerRadius属性时,如果视图同时具有不透明背景色或复杂的背景图像,可能会触发离屏渲染。
  2. 阴影(Shadows):设置layer的shadow属性(如shadowColor、shadowOffset、shadowRadius等)会产生阴影效果,这些效果通常需要离屏渲染。
  3. 透明度(Opacity):当视图的alpha值小于1或使用了CALayer的opacity属性时,如果有复杂混合层级,可能触发离屏渲染。
  4. 遮罩(Masking):使用CALayer的mask属性或UIView的maskView时,遮罩效果通常需要离屏渲染。
  5. 非默认混合模式:当视图或图层使用非默认的混合模式(如multiply、screen、overlay等)时,系统可能需要在离屏缓冲区中进行混合操作。
  6. 多重渲染目标(Multiple Render Passes):需要多次渲染才能完成的效果,如复杂动画、多重叠加效果等,可能需要离屏缓冲区进行中间结果的存储和合并。

为什么某些效果不行:

某些效果需要在渲染过程中进行多次像素级的处理,这在帧缓冲区的单次渲染流程中难以实现。例如,阴影效果需要在原始图层渲染后,再在其周围绘制额外的阴影像素,这涉及到对已经渲染的像素进行二次处理,因此需要在离屏缓冲区中先进行渲染,然后再与主帧缓冲区的内容合并。

优化建议:

  • 避免不必要的离屏渲染:例如,对于圆角效果,可以考虑使用系统提供的圆角属性,而不是通过离屏渲染实现。
  • 合理利用视图层级关系:在iOS中,视图层级关系会影响渲染的优先级,可以通过调整视图的层级来优化渲染性能。
  • 使用offscreen rendering进行调试:通过打开offscreen rendering的调试选项,可以观察到应用在进行离屏渲染时的具体情况,帮助定位性能瓶颈。

通过深入理解离屏渲染的原理并采取有效的优化措施,可以提升应用的性能和用户体验。在实际开发中,应尽量避免不必要的离屏渲染操作,合理利用视图层级关系和Metal API进行自定义渲染,从而打造出流畅、高效的iOS应用。