当前位置: 首页 > news >正文

【iOS】渲染原理离屏渲染

渲染原理&&离屏渲染

  • 图像渲染流程
      • Application
      • Geometry
      • Resterization
      • Pixel像素处理阶段:处理像素,得到位图
  • 图像渲染技术栈
    • UIKit
    • Core Animation
    • Core Graphics
    • Core Image
    • OpenGL ES
    • Metal
  • UIView与CALayer的关系
    • CALayer
      • contents image
      • contents Drawing
  • Core Animation流水线
    • Commit Transation
      • Layout:构建视图
      • Display:绘制视图
      • prepare:Core Animation额外的工作
      • commit:打包并发送
    • Rendering Pass:Render Server的具体操作
  • 离屏渲染
    • 离屏渲染产生的原因
      • 圆角的离屏渲染
      • 触发离屏渲染的几种情况
    • 离屏渲染对性能的影响
    • 离屏渲染的优化策略

图像渲染流程

在这里插入图片描述

这里只有Application阶段是由CPU负责,后续都是由GPU负责

Application

该阶段目的主要是得到图元,指的是图像在应用中被处理的阶段,此时还处于CPU负责的时期,在这个阶段可以会对图像进行一系列的操作或者改变,最终将新的图像信息传给下一个阶段。这部分信息被叫做图元

[!TIP]

图元就是描述几何形状的基本元素,通常为三角形、线段、顶点等

Geometry

该阶段主要是几何处理阶段,从这个阶段开始,包括以后的阶段都是主要由GPU负责了。这个时候GPU可以拿到上一个阶段传递下来的图元信息,GPU会对这部分图元通过顶点着色器、形状装配、几何着色器进行处理,之后输出新的图元。这一系列阶段包括:

  • 顶点着色器:该阶段中会将图元中的顶点信息进行视角的转换,添加光照信息、增加纹理等操作
  • 形状装配:图元中的三角形、线段、点分别对应三个Vertex、两个Vertex、一个Vertex。这个阶段会将Vertex连接成相对应的形状
  • 几何着色器:额外添加额外的Vertex,将原始图元转换成新的图元,以构建一个不一样的模型。简单的说就是基于通过三角形、线段和点构建成更负责的几何图形。

Resterization

光栅化的主要目的是将几何渲染之后的图元信息,转换为一系列的像素,以便后续显示在屏幕上

工作原理:根据图元信息,计算出每个图元所覆盖的像素信息等,从而将像素划分成不同的部分

在这里插入图片描述

以本图为例:中心点若是在图元内部,那么这个像素就属于这个图元。

Pixel像素处理阶段:处理像素,得到位图

在上面的光栅化处理之后,我们得到了图元对应的像素,之后我们需要通过片段着色器(给每一个像素Pixel赋予正确的颜色)和测试与混合(处理片段的前后位置以及透明度)给这些像素填充正确的颜色和小锅得到位图以便最终显示在屏幕上。

而经过了处理、蕴含着大量信息的像素点集合,就被称作位图

  • 片段着色器:**该阶段的目的是给每一个像素Pixel赋予正确的颜色。**颜色的来源就是之前得到的顶点、纹理、光照等信息。这里需要处理纹理、光照等复杂信息,所以这通常是整个系统的性能瓶颈。
  • 测试和混合:这个阶段主要的目的是处理片段的前后位置以及透明度,这个阶段会检测各个做色片段的深度值z坐标,以判断片段的墙后位置,以及是否应该被舍弃。同时也会计算响应的透明度,进行片段的混合,得到最终的颜色。

图像渲染技术栈

整个图像渲染技术栈:APP使用Core GraphicsCore AnimationCore Image等框架来绘制可视化内容,这些软件框架之间相互依赖。这些框架都需要通过OpenGL来调用GPU绘制,最终将内容显示到屏幕之上

在这里插入图片描述

UIKit

UIKit框架提供了iOS所需的基本架构,提供了用于实施界面的窗口和视图架构,用于向APP提供多点触控和其他类型输入的时间处理基础架构,以及管理用户、系统、app之间互动所需的主运行循环。

Core Animation

这是一个复合引擎,职责是尽可能快地组合屏幕上不同的可视内容,这些可视内容可被分解成独立的图层,这些图层会被存储在一个叫做图层树的体系之中。本质来说,CALayer是用户所能在屏幕上看见一切的基础。

Core Graphics

这是基于Quartz高级绘图引擎,主要用于运行时绘制图像。开发者可以使用这个框架来处理基于路径的绘图、转换、颜色管理、离屏渲染、图案、渐变和阴影,图像数据管理,图像创建和图像遮罩以及PDF文档创建,显示和分析。

当我们需要运行时创建图像的时候,可以使用Core Graphics去绘制。与之相对应的是运行前创建图像

Core Image

Core ImageCore Graphics相反,用于处理运行前创建的图像。其拥有一系列线程的图像过滤器,能对已存在的图像进行高效的处理。大多数情况,Core Image会在GPU中完成工作。

OpenGL ES

这是OpenGL的子集。而OpenGL是一套第三方标准,函数内部的实现由对应的GPU厂商开发实现

Metal

Metal类似于OpenGL ES,这也是一套第三方标准,具体由Apple实现。开发者都在间接的使用MetalCore AnimationCore ImageSceneKitSpriteKit 等等渲染框架都是构建于 Metal 之上的。

UIView与CALayer的关系

CALayer事实上是用户所能在屏幕上看见的一切的基础。对于UIKit来说,每一个UI视图空间内部其实都有一个关联的CALayer,正是这种一一对应的关系,视图层级拥有视图树的树形结构,对应CALayer层级也拥有图层树的树形结构:

为什么iOS要基于UIView和CALayer提供两个平行的层级关系?

这里的原因是要做到职责分离,这样能避免有很多重复的代码。

CALayer

CALayer等同于一个纹理。纹理是GPU进行图像渲染的重要依据。纹理本质上来说就是一张图片,故而CALayer包含一个contents属性指向一块缓存区,称作backing store,可以存放位图。iOS中将该缓存区保存的图片成为寄宿图。

在这里插入图片描述

在实际开发中,iOS有两种相应的方式去绘制界面

  • 使用图片:contents image
  • 手动绘制:custom drewing

contents image

这是指CALayer的contents属性来设置图片。本质上,contents属性指向的一块缓存区域,称为backing store,可以存放bitmap数据。

contents Drawing

这是指使用Core Graphics来直接绘制寄宿图。在实际开发中,一般通过继承UIview实现drawRect:方法来自自定义绘制。

虽然drawRect:是一个UIview方法,但是事实上都是底层的CALayer完成了重绘工作并且保存了图片。这里大概展示一个drawRect绘制定义寄宿图的基本原理。

在这里插入图片描述

Core Animation流水线

在这里插入图片描述

APP本身其实并不负责渲染,渲染是由一个独立的进程负责,即Render Server进程。

APP通过IPC将渲染任务以及相关的数据交给1Render Server,其处理完数据以后,再传递至GPU。最后由GPU调用iOS图像设备进行显示。

Core Animation流水线的工作流程:

  • Handle Events:APP响应事件,改变视图/图层,触发渲染流水线开始
  • Commit Transaction:在CPU上处理显示内容的前置计算,包含布局、显示、准备、提交四个阶段,发生在应用程序进程中。
  • Decode:打包好的图层被传输到Render Server,之后,首先进行解码。注意完成解码之后需要等待下一个Runloop才会执行,下一步Draw Calls
  • Render:这是由GPU进行渲染
  • DIsplay:显示阶段,需要等render结束下一个Runloop的触发显示

对上述步骤串联,执行所消耗的事件超过了16.67ms,因此为了满足屏幕的60FPS刷新率的支持,需要将这些步骤进行分解,通过流水线的方式进行并线执行,下图所示:

在这里插入图片描述

Commit Transation

Core Animation流水线中,app调用Render Server前的最后一步Commit Transation其实可以细分4个步骤:

Layout:构建视图

在该阶段主要处理视图构建和布局,具体步骤包括以下几步:

  • 调用重载的layoutSubviews方法
  • 创建视图,通过addSubview方法添加子视图
  • 计算视图布局,所有的Layout Constraint

Display:绘制视图

在该阶段主要是交给Core Graphics进行视图的绘制以得到图元数据:

  • 根据俄上个阶段Layout的结果创建得到图元信息
  • 重写drawRect:方法,会调用重载的drawRect方法,在drawRect方法中手动绘制得到bitmap数据,从而自定义视图的绘制

正常情况下Display阶段这能得到图元信息,但是重写了drawRect方法,这个方法会直接调用Core Graphics绘制方法得到bitmap数据,同时系统会额外申请一块内存,去保存暂存的bitmap。

由于我们重写了drawRect:方法,导致绘制过程从GPU转移到了CPU,这会导致一定的效率损失。这个时候,这个过程会额外使用CPU和内存,因此需要高效绘制,否则容易造成CPU卡顿或者内存爆炸。

prepare:Core Animation额外的工作

主要进行图片解码和转换

commit:打包并发送

这一步主要是:图层打包并发送Render Server

注意commit操作是依赖图层树递归执行的,所以如果图层树过于复杂,commit的开销就会很大,这也是为什么我们希望减少视图层级,降低图层树复杂度的原因。

Rendering Pass:Render Server的具体操作

在这里插入图片描述

Render Server通常是OpenGL或者是Metal

若是OpenGL的话,那么上图中主要是GPU中执行的操作,具体主要包括:

  • GPU收到包括图元信息的Command Buffer
  • Tiler开始工作:先通过顶点着色器Vertex Shader对顶点进行处理,更新图元信息。
  • 平铺过程:平铺生成title bucket几何图形,这一步会将图元信息转化为像素,之后再将结果写入Paramerter buffer
  • Tiler更新完所有的图元信息,或者Parameter Buffer已经满了,则会开始下一步
  • Renderer工作:将像素信息进行处理得到bitmap,之后存入Render Buffer
  • Render BUffer中存储有渲染好的bitmap,供之后的display操作使用

离屏渲染

简单来说,如果不能一次性得到渲染结果,必须要一次或者多次渲染将结果缓存存入到内存区域,最后再结合后写入到Frame Buffer,这样的渲染就被称为离屏渲染

离屏渲染产生的原因

iOS中主要的渲染操作都是由Render Server模块通过调用显卡驱动所提供的OpenGL/Metal接口来执行的。通常对于每一层layer,Render Server会遵循画家算法,按次序输出到frame Buffer,之后一层一层的覆盖,就能得到最终的显示结果。

在这里插入图片描述

有些场景没有那么简单。作为画家的GPU一层一层的向画布上进行输入,但是其没有办法在某一层渲染完成之后回过头去更改某一部分,这是由于这一层之前的若干层layer像素数据,已经在渲染中被永久覆盖了。这也就意味着对于每一次layer而言,要么找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂,多次修改/裁剪操作。

圆角的离屏渲染

若是只设置了layercornerRadius而没有设置masksToBounds,由于不需要叠加裁剪,这个时候是不会触发离屏渲染。而当设置了裁剪属性的时候,由于masksToBounds会对layer以及所有的subLayercontent都进行裁剪,所以不得不触发离屏渲染

在这里插入图片描述

在普通的layer绘制中,上层的sublayer会覆盖下层的sublayer,下层的sublayer绘制完成之后就可以抛弃了。所有sublayer依次绘制完毕之后,整个绘制过程完成,就可以进行后续的呈现了。

在这里插入图片描述

但是设置了cornerRadius以及masksToBounds进行圆角裁剪的时候,masksToBounds裁剪属性会应用到所有sublayer上面,也就是说所有的sublayer在第一次被绘制之后不能立刻被丢弃还需要保存在Offscreen buffer中等待下一轮圆角 +裁剪,这就会导致离屏渲染。

触发离屏渲染的几种情况

  1. 使用了mask的layer(layer.mask)
  2. 需要进行裁剪的layer(layer.masksToBounds/view.clipsToBounds)
  3. 设置了组透明度为YES,并且透明度不为1的layer(layer.allowsGroupOpacity/layer.opacity)
  4. 添加了投影的layer(layer.shadow)
  5. 采用了光栅化的layer(layer.shouldRasterize)
  6. 绘制了文字的layer(UILabelCATextLayerCore Text)

离屏渲染对性能的影响

首先,明确离屏渲染会增大GPU的负担

  • GPU的工作是高度流水线化的,不断向Frame Buffer输出,若是出现离屏渲染的情况,则GPU流水线不得不先切换上下文,将中间的结果输出到Offscreen Buffer,等到中间过程完成后再组成所有中间结果输出到Frame Buffer。如果GPU频繁切换上下文,将严重影响GPU的情况,导致掉帧的情况。
  • 离屏渲染需要额外的存储空间

离屏渲染的优化策略

  1. 避免不必要的圆角:对于静态内容,可以预先将图片裁剪为带圆角的版本;对于动态内容,考虑使用UIBezierPath或者Core Graphics绘制圆角;或者使用maskView代替cornerRadius
  2. 简化阴影效果:减少阴影的模糊半径、调整阴影颜色以降低alpha值、避免在频繁变动的视图上使用阴影,或者使用模拟阴影的视觉技巧(如渐变背景)替代。
  3. 调整透明度与混合模式:尽量避免不必要的透明度设置,尤其是对于大面积或层级较深的视图。对于混合模式,评估是否可以使用视觉效果相近但性能更好的模式,或者避免在性能敏感区域使用非默认混合模式。
  4. 使用硬件加速功能:如shouldRasterize属性可以让图层内容预先渲染为位图,减少后续绘制时的计算量。但要注意过度使用可能导致内存增加,需权衡利弊。
  5. 布局与层级优化:减少不必要的视图层级和重叠部分,避免深度过大的视图结构。这有助于减少离屏渲染的需求并提高渲染效率。
  6. 长列表优化:对于滚动列表,使用UICollectionView或UITableView,并结合cell prefetching、estimatedItemSize、dequeueReusableCell等技术减少不必要的视图创建和销毁,降低离屏渲染的概率。
  7. 适时使用异步绘制:对于复杂的绘制任务,特别是在主线程中可能导致卡顿或掉帧的情况下,考虑使用异步绘制技术。虽然UIKit框架本身并不直接支持异步绘制UIView,但你可以利用Core Graphics(Quartz 2D)或Metal等更低级别的图形API来在后台线程上执行绘制操作,并将结果作为图像(UIImage)或图层内容(CALayer的contents)在主线程上更新。这样做可以显著减少主线程的负载,提高应用的响应性和流畅度。
  8. 缓存绘制结果:对于不会频繁变化的复杂视图或图形,可以将其绘制结果缓存为位图(UIImage或CGBitmapContext)。这样,每次需要显示时,只需从缓存中取出位图进行展示,而无需重新进行复杂的绘制操作。缓存技术可以大幅度减少渲染时间,提高性能。但需要注意的是,缓存也会占用额外的内存资源,因此需要合理管理缓存的大小和生命周期。
  9. 优化图像资源:图像资源是应用中最常见的渲染对象之一。优化图像资源可以显著减少渲染时间和内存占用。这包括使用合适的图像格式(如PNG、JPEG等),根据设备屏幕分辨率提供不同尺寸的图像,以及使用图像压缩算法减少图像文件的大小。此外,对于需要动态修改的图像(如添加滤镜、圆角等),考虑在图像加载时就进行处理,并将处理结果缓存起来,以避免在每次显示时都进行重复的计算和渲染。
  10. 利用iOS 13及以上版本的Metal Performance Shaders(MPS):如果你的应用需要处理大量的图形数据或执行复杂的图形计算,可以考虑使用Metal Performance Shaders(MPS)来加速渲染过程。MPS是Apple为Metal框架提供的一组高性能计算内核,专门用于图像处理和图形计算任务。通过使用MPS,你可以将复杂的图形算法以更高效的方式实现,并充分利用GPU的并行计算能力来加速渲染速度。
  11. 分析和测试:在进行离屏渲染优化时,不要忘记使用Xcode提供的工具(如Instruments的Core Animation模板)来分析和测试你的应用性能。这些工具可以帮助你识别哪些视图或图层触发了离屏渲染,并测量渲染所需的时间和资源消耗。通过分析测试结果,你可以更准确地定位性能瓶颈,并采取相应的优化措施。
  12. 代码审查和重构:定期进行代码审查和重构也是提高渲染性能的重要手段。通过审查代码,你可以发现并消除不必要的离屏渲染操作,如不必要的阴影、透明度设置或混合模式等。同时,通过重构代码,你可以优化视图层级结构、减少视图数量、合并相似的绘制逻辑等,从而进一步提高渲染效率。
http://www.lryc.cn/news/609846.html

相关文章:

  • 网络安全 | 从 0 到 1 了解 WAF:Web 应用防火墙到底是什么?
  • 国内首个开源SCA社区——OpenSCA开源社区
  • C++临时对象:来源与性能优化之道
  • 前后端流式交互的几种方式
  • Petalinux快捷下载
  • 【笔记】ROS1|2 Turtlebot3汉堡Burger连接和远程控制【旧文转载】
  • 【SpringAI】SpringAI的介绍与简单使用
  • 算力板卡:驱动智能时代的核心引擎
  • File、IO流体系
  • 防御保护综合练习
  • 关键领域软件研发如何构建智能知识管理体系?从文档自动化到安全协同的全面升级
  • 详解Python标准库之通用操作系统服务
  • ZeroNews内网穿透安全策略深度解析:构建企业级安全连接体系
  • 【2025】想曰(yue)免费开源的文本加密软件,保障隐私安全
  • 福彩双色球第2025089期篮球号码分析
  • 竞品分析爬虫实现方案
  • 人类学家与建筑师:解析 UX 研究与项目管理的需求分析差异​
  • Opencv[一]
  • # 自动定时运行Python爬虫脚本教程(Windows任务计划程序)
  • 项目实战二:RPC
  • 17.6 超拟人大模型CharacterGLM技术解析:92.7%角色一致性+虚拟偶像互动提升300%,如何吊打GPT-4?
  • C++-异常
  • Python----大模型(量化 Quantization)
  • MySQL详解(一)
  • 从零开始的云计算生活——项目实战
  • 商标续展如果逾期了还有办法补救吗?
  • 消息系统技术文档
  • 学习嵌入式第十九天
  • 系统一个小时多次Full GC,导致系统线程停止运行,影响系统的性能,可靠性
  • 靶场(二十八)---小白心得靶场体会---Mantis