OpenHarmony 渲染原理分析
基于
foundation/graphic/graphic_2d/源码(Rosen 渲染框架)
目录
术语
| 缩写 | 全称 | 说明 |
|---|---|---|
| Rosen | — | OHOS 自有渲染框架名称 |
| RSNode | Render Service Node | 渲染节点(Client 端 UI 树的节点) |
| Transaction | — | 跨进程渲染数据包(节点变更序列化) |
| Drawable | — | Server 端的可绘制对象(由 RSNode 生成) |
| HWC | Hardware Composer | 硬件合成器(显示控制器硬件加速) |
| VSync | Vertical Synchronization | 垂直同步信号,帧同步基准 |
| HDI | Hardware Display Interface | 显示硬件抽象层 |
| DRM | Direct Rendering Manager | Linux 内核显示驱动框架 |
| KMS | Kernel Mode Setting | 内核级显示模式设置 |
| HGM | Huawei Graphics Management | 动态帧率管理模块 |
| AFBC | ARM Frame Buffer Compression | ARM 帧缓冲压缩 |
| Dirty Region | — | 脏区域,仅此区域需重绘 |
| ArkUI | — | OHOS 声明式 UI 框架(类 SwiftUI/Compose) |
1. 概述
OpenHarmony 的渲染系统采用 Client-Server 架构:应用进程作为 Client 提交渲染指令和像素数据,render_service 作为 Server 负责合成所有窗口并输出到显示硬件。这种架构隔离了各应用的渲染上下文——一个应用的崩溃不会影响其他应用的显示,同时统一了合成调度,确保帧率稳定。
全文章节路线图
VSync 信号 (第2章)
│ 硬件中断触发帧同步
▼
应用进程 (第3章)
│ 构建 UI 节点树 → Canvas 绘制 → 打包 Transaction + BufferQueue
│
├──→ render_service 决策 (第4章)
│ 判断哪些窗口需要绘制、哪些可以跳过
│
▼
render_service 处理 (第5章)
│ 主线程 → 渲染线程 → HWC/Display 处理
│
├──→ GPU 合成 或 HWC 合成 (第6章)
│
▼
帧提交上屏 (第7章)
│ HDI → DRM/KMS → 显示硬件
设计目标
| 目标 | 实现方式 | 对应章节 |
|---|---|---|
| 高性能 | 三线程流水线(Main/Render/Hardware),并行处理 | 第5章 |
| 低功耗 | 脏区域重绘、遮挡剔除、动态帧率 (HGM)、AFBC 压缩 | 第4章、第9章 |
| 跨平台 | Skia 作为 2D 图形引擎,支持 OpenGL/Vulkan/Raster 三种后端 | 第5章 |
| 灵活合成 | GPU Composition + HWC 硬件合成动态切换 | 第6章 |
| 多窗口 | 节点树管理多窗口 Z-Order,独立控制可见性和合成策略 | 第3章、第4章 |
核心链路
┌────────────────────────────────────────────────────────────┐
│ 应用进程 (Client) │
│ ┌──────────────────┐ ┌────────────────────────────┐ │
│ │ ArkUI 组件树 │ │ render_service_client │ │
│ │ (状态 → UI 树) │ │ RSNode 操作 → Transaction │ │
│ └──────────────────┘ └────────────┬───────────────┘ │
│ │ │
│ ┌── Transaction 通道 ──────────┐ │ IPC (MessageParcel) │
│ │ 节点命令/属性/动画 │ │ │
│ └──────────────────────────────┘ │ │
│ │ │
│ ┌── BufferQueue 通道 ──────────┐ │ 共享内存 / DMA-BUF │
│ │ 像素数据 (Canvas 绘制结果) │ │ │
│ └──────────────────────────────┘ │ │
└─────────────────────────────────────┼──────────────────────┘
│ Transaction + BufferQueue 双通道
┌─────────────────────────────────────┼──────────────────────┐
│ render_service 进程 (Server) │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Main Thread (主线程) │ │
│ │ ConsumeBuffer + ProcessTransaction │ │
│ │ → Animate → Prepare → Process │ │
│ │ → PostAndWait → Commit │ │
│ └────────────────────┬─────────────────────────┘ │
│ │ PostAndWait() │
│ ┌────────────────────▼─────────────────────────┐ │
│ │ Render Thread (渲染线程) │ │
│ │ Sync → GPU Render → HWC Set │ │
│ └────────────────────┬─────────────────────────┘ │
│ │ │
│ ┌────────────────────▼─────────────────────────┐ │
│ │ HDI → DRM/KMS → 显示硬件 │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
渲染栈层次
每一层向上层提供抽象接口,下层无需关心上层的具体实现——例如 Skia 屏蔽了 OpenGL 与 Vulkan 的差异,HDI 屏蔽了不同显示硬件的差异。
层次 职责 本文章节
──── ──── ────
ArkUI 声明式 UI 框架 第3章
render_service_client ROSEN Client API 第3章
IPC MessageParcel 传输 第3章
render_service ROSEN Server 第4、5章
Skia (2D Graphics) Canvas/Paint/Path 绘制 第5章
GPU Backend OpenGL / Vulkan / Raster 第6章
HDI / Display Engine 显示硬件抽象 第7章
DRM / KMS Linux 内核显示驱动 第7章
显示硬件 屏幕扫描输出 —
2. VSync 信号触发
2.1 VSync 组件体系
VSync 系统由以下组件组成,定义在 foundation/graphic/graphic_2d/rosen/modules/render_service/vsync/ 中:
RSVsyncManager (管理器,单例)
├── VSyncGenerator ← 硬件 VSync 信号生成器
├── VSyncSampler ← 信号采样器(计算实际刷新率)
├── VSyncController ← 信号控制器(应用/渲染两条独立通道)
│ ├── appVSyncController ← 应用 VSync 通道
│ └── rsVSyncController ← 渲染服务 VSync 通道
├── VSyncDistributor ← 信号分发器(通知订阅者)
│ ├── appVSyncDistributor ← 分发给应用进程
│ └── rsVSyncDistributor ← 分发给渲染服务
└── VSyncConnection ← 连接对象(每个订阅者一个)
2.2 VSync 信号流程
硬件 VBlank 中断
│
▼
VSyncGenerator
│ 生成 VSync 信号(基于硬件中断或软件定时器)
│ 信号频率 = 屏幕刷新率(60Hz / 90Hz / 120Hz)
▼
VSyncSampler
│ 采样 vsync 周期,计算实时刷新率
│ 用于 HGM (Huawei Graphics Management) 动态调频
▼
VSyncController (两条独立通道)
├── appVSyncController ← 控制应用侧帧率
│ │ 应用在收到 app vsync 后开始布局/绘制
│ ▼
│ appVSyncDistributor
│ │ 分发给所有应用进程的 VSyncConnection
│ ▼
│ 应用进程 → 开始 UI 线程帧处理
│
└── rsVSyncController ← 控制渲染服务侧帧率
│ 渲染服务在收到 rs vsync 后开始合成
▼
rsVSyncDistributor
│ 分发给渲染服务内部
▼
主线程唤醒 → 开始一帧处理
2.3 双 VSync 通道设计
应用和渲染服务各有一个独立的 VSync 控制器,实现应用帧率与合成帧率解耦:
- 应用帧率可低于屏幕刷新率(如 30fps 动画)
- 渲染服务帧率始终匹配屏幕刷新率
- 两条通道相互独立,但硬件中断源相同
2.4 VSync 信号与帧率管理 (HGM)
HgmCore(位于 foundation/graphic/graphic_2d/rosen/modules/render_service/core/feature/hgm/)根据场景动态调整刷新率:
| 场景 | 刷新率 | 功耗策略 |
|---|---|---|
| 静态页面(无动画) | 60Hz → 30Hz 或更低 | 省电 |
| 普通 UI 动画 | 60Hz | 均衡 |
| 高帧率游戏 | 90Hz / 120Hz | 性能优先 |
| 视频播放(24fps) | 匹配视频帧率 | 省电 |
HGM 通过 VSyncSampler 实时监控实际帧率,结合当前场景标签决策刷新率。
3. 应用侧:Client 端渲染准备
应用进程负责 UI 树的构建、布局计算和绘制指令生成。每一帧的处理在收到 VSync 信号后开始,在 Transaction 提交后结束:
应用进程收到 VSync (appVSyncDistributor)
│
├── ArkUI 状态管理(响应式状态变更)
├── UI 组件树重绘(布局计算)
├── Canvas 绘制(Skia 绘制指令)
├── 节点操作(RSNode 创建/属性变更/动画)
│
├── 打包 RSTransactionData(节点命令 + 元数据)
├── QueueBuffer(像素数据 → BufferQueue)
│
└── CommitTransaction() → IPC 发送到 render_service
第3章涵盖 Client 端的三项核心工作:
| 工作 | 对应小节 | 说明 |
|---|---|---|
| 节点树构建 | 3.1 | 管理 UI 组件的渲染节点层次结构 |
| Transaction 打包 | 3.2 | 序列化节点变更命令通过 IPC 发送 |
| BufferQueue 传输 | 3.3 | 将 Canvas 绘制的像素数据通过共享内存传输 |
3.1 节点树构建
应用通过 render_service_client API 构建渲染节点树:
RSSurfaceNode (每个应用窗口一个)
└── RSCanvasNode (UI 组件)
├── RSCanvasNode (子组件)
│ ├── RSProperty (位置/大小/旋转/透明度)
│ ├── RSModifier (动画/变换)
│ └── RSDisplayNode (内部绘制指令)
└── ...
节点类型:
| 节点类型 | 用途 |
|---|---|
RSSurfaceNode |
应用窗口的根节点,对应一个 Surface |
RSCanvasNode |
一般 UI 组件的渲染节点 |
RSDisplayNode |
显示节点,对应一个物理屏幕或虚拟屏 |
RSRootNode |
节点树的根 |
RSProxyNode |
代理节点,用于跨进程引用 |
3.2 Transaction 打包
应用在一次 VSync 周期内完成 UI 布局和绘制后,将所有节点变更打包为 RSTransactionData 发送给 render_service。
RSTransactionData 结构
定义在 foundation/graphic/graphic_2d/rosen/modules/render_service_base/include/transaction/rs_transaction_data.h:
class RSTransactionData : public Parcelable {
// 核心载荷:命令列表
std::vector<std::tuple<NodeId, FollowType, std::unique_ptr<RSCommand>>> payload_;
// 每个元素 = (目标节点ID, 跟随类型, 命令对象)
// 元数据
uint64_t timestamp_; // 时间戳(应用侧生成)
pid_t pid_; // 发送进程 ID
pid_t tid_; // 发送线程 ID
uint64_t index_; // 命令序号
std::string abilityName_; // 所属 Ability 名称
// 同步控制
bool needSync_; // 是否需要同步等待
bool needCloseSync_; // 是否需要关闭同步
uint64_t syncId_; // 同步标识符
int32_t syncTransactionCount_; // 同步命令计数
int32_t parentPid_; // 父进程 ID
// DVSync(动态帧率)
bool dvsyncTimeUpdate_; // DVSync 时间是否更新
uint64_t dvsyncTime_; // DVSync 时间戳
// 缓存控制
bool isCached_; // 是否已缓存
uint32_t parcelNumber_; // Parcel 编号
};
payload_ 的核心内容
payload_ 是 RSTransactionData 的核心,包含一帧中所有的节点操作命令。每个命令由 <NodeId, FollowType, RSCommand> 三元组构成:
| 字段 | 类型 | 说明 |
|---|---|---|
NodeId |
uint64_t |
目标渲染节点的 ID(全局唯一) |
FollowType |
enum | 命令跟随类型(NODE / SURFACE / FOLLOW_PARENT) |
RSCommand |
多态基类 | 具体的节点操作命令 |
RSCommand 类型
命令类型通过 RSCommandType 枚举标识(rs_command.h):
| 类型 | 值 | 用途 | 典型命令 |
|---|---|---|---|
BASE_NODE |
0 | 基础节点操作 | 创建/销毁节点、设置位置/大小/旋转/透明度 |
CANVAS_NODE |
2 | Canvas 节点操作 | 设置绘制指令列表(DrawCmdList) |
SURFACE_NODE |
3 | Surface 节点操作 | 设置 Surface Buffer、裁剪区域 |
ANIMATION |
9 | 动画操作 | 创建/启动/停止动画、设置动画参数 |
PROXY_NODE |
4 | 代理节点操作 | 跨进程节点引用 |
ROOT_NODE |
5 | 根节点操作 | 根节点设置 |
DISPLAY_NODE |
6 | 显示节点操作 | 显示属性设置 |
EFFECT_NODE |
7 | 特效节点操作 | 模糊/滤镜/阴影 |
UNION_NODE |
13 | 合并节点操作 | 多节点合并绘制 |
FRAME_RATE_LINKER |
12 | 帧率链接器 | 帧率控制 |
一次 Transaction 打包和发送
应用 UI 线程完成布局
│
├── RSCanvasNode::SetBounds(width, height) → 生成 BASE_NODE 命令
├── RSCanvasNode::SetPosition(x, y) → 生成 BASE_NODE 命令
├── RSCanvasNode::SetAlpha(0.5) → 生成 BASE_NODE 命令
├── RSCanvasNode::SetPaintDrawCmdList(...) → 生成 CANVAS_NODE 命令
├── RSAnimation::Start() → 生成 ANIMATION 命令
│
├── 所有命令加入当前帧的 RSTransactionData
│ payload_.push_back({ nodeId, NODE, command_ptr });
│
└── RSTransactionProxy::FlushImplicitTransaction()
│
├── Marshalling() → 序列化到 MessageParcel
│ ├── timestamp_ + pid_ + index_
│ ├── 每个 RSCommand 序列化(类型 + 参数)
│ └── sync 控制字段
│
└── IPC 发送 → render_service 进程
序列化与传输
RSTransactionData 实现 Parcelable 接口,通过 IPC 跨进程传输:
// Marshalling:序列化(应用侧 → Parcel)
bool RSTransactionData::Marshalling(Parcel& parcel) const {
parcel.WriteUint64(timestamp_); // 写时间戳
parcel.WriteInt32(pid_); // 写进程 PID
parcel.WriteUint64(index_); // 写序号
// ... 各控制字段
parcel.WriteUInt32(payload_.size()); // 写命令数
for (auto& [nodeId, followType, cmd] : payload_) {
parcel.WriteUint64(nodeId); // 写节点 ID
parcel.WriteUint16(cmd->GetType());// 写命令类型
cmd->Marshalling(parcel); // 写命令参数
}
}
// Unmarshalling:反序列化(Parcel → render_service 侧)
RSTransactionData* RSTransactionData::Unmarshalling(Parcel& parcel) {
auto data = new RSTransactionData();
parcel.ReadUint64(data->timestamp_);
parcel.ReadInt32(data->pid_);
// ... 读取各字段 + 命令列表
return data;
}
Transaction 在 render_service 端处理
render_service 收到 IPC 消息
│
├── RecvRSTransactionData() → 存入 TransactionDataMap
│ TransactionDataMap = std::unordered_map<pid_t, vector<unique_ptr<RSTransactionData>>>
│ └── 按 pid 分组:应用 A 的 Transaction / 应用 B 的 Transaction
│
├── VSync 到达后,主线程统一处理
│
├── RSTransactionData::Process(context)
│ └── 遍历 payload_:对每个 (nodeId, followType, cmd)
│ ├── 在节点树中找到 nodeId 对应的 RSNode
│ └── cmd->Process(context) → 执行命令(更新节点属性)
│
└── ProcessCommand() 循环处理所有 Transaction
直到所有应用的命令全部应用完毕
3.3 绘制数据的传输:BufferQueue
Transaction 只传递"命令"(节点属性、动画参数等),不传递"像素数据"。 实际的渲染内容(Canvas 绘制结果)通过另一条通道——BufferQueue——传输。
Transaction + BufferQueue 双通道
应用进程 render_service 进程
│ │
│ ┌── Transaction 通道 ──────────┐ │
│ │ IPC (MessageParcel) │ │
│ │ 传输:节点创建/属性/动画命令 │ │
│ │ 频率:每帧一次 │ │
│ └─────────────────────────────┘ │
│ │
│ ┌── BufferQueue 通道 ──────────┐ │
│ │ 共享内存 / 硬件 Buffer │ │
│ │ 传输:像素数据(Surface 内容)│ │
│ │ 频率:每帧一次 │ │
│ └─────────────────────────────┘ │
│ │
BufferQueue 工作机制
每个 RSSurfaceRenderNode(对应一个应用窗口)关联一个 RSSurfaceHandler,内部持有 IConsumerSurface(render_service 端)和 IProducerSurface(应用端):
// RSSurfaceHandler 持有消费者端 Surface
class RSSurfaceHandler {
sptr<OHOS::IConsumerSurface> consumer_; // render_service 端,接收 Buffer
// ...
};
应用侧 (Producer) render_service 侧 (Consumer)
│ │
├── Skia/GPU 渲染到 Buffer │
│ (从 BufferQueue 中 dequeue 一块) │
│ │
├── IProducerSurface::QueueBuffer(buffer)│
│ └── Buffer 入队 → 通知 Consumer │
│ │
│ OnBufferAvailable() ← 回调
│ │
│ ConsumeAndUpdateAllNodes()
│ │ 遍历所有 SurfaceNode
│ │ 对每个有 Buffer 的 Surface:
│ │ RSBaseSurfaceUtil::ConsumeAndUpdateBuffer()
│ │ ├── AcquireBuffer() 获取 Buffer
│ │ ├── 记录 Buffer ID、时间戳
│ │ └── 标记 Surface Dirty
│ │
│ ▼
│ RSMainThread::Prepare()
│ │ RSUniRenderVisitor 使用 Buffer
│ │ 进行 GPU Composition 或传给 HWC
│ ▼
│ Render Thread
│ │ GPU 纹理引用 Buffer 的共享内存
│ │ 或 HWC 直接使用 Buffer 的 fd
│ ▼
│ Display Commit
完整的一帧数据流
应用侧:
1. 状态更新 → UI 树计算
2. Canvas 绘制 → GPU 渲染到 Buffer A
3. QueueBuffer(Buffer A)
4. 创建 Transaction(携带节点属性变更)
5. CommitTransaction() → IPC 发送
↑ 两条数据独立传输
render_service 侧:
6. OnBufferAvailable() → 回调通知
7. RecvRSTransactionData() → 收到命令
8. VSync 到达:
a. ConsumeAndUpdateAllNodes() → AcquireBuffer(Buffer A)
b. ProcessCommand() → 应用节点属性
c. Prepare() → 计算脏区域、遮挡剔除
d. Render Thread → GPU 合成或 HWC 合成
e. Display Commit → 帧上屏
关键区别:
| 通道 | 传输内容 | 实现方式 | 触发时机 |
|---|---|---|---|
| Transaction | 节点属性、动画、命令 | IPC (MessageParcel) | 每帧结束时 |
| BufferQueue | 像素数据(Surface 内容) | 共享内存 / DMA-BUF | 渲染完成后立即 Queue |
| 两者关系 | Transaction 描述"节点变成什么样",Buffer 描述"节点画了什么" | 两者独立传输,在合成阶段汇合 |
4. 如何判断哪些窗口需要绘制
render_service 通过三阶段决策,确定每一帧需要绘制哪些窗口:
阶段一:Transaction 触发(谁提交了变更)
↓
阶段二:Dirty 标记传播(哪些节点脏了)
↓
阶段三:可见性裁剪(哪些节点当前可见)
4.1 阶段一:Transaction 触发
应用进程在每一帧结束时将节点变更打包为 RSTransactionData,通过 IPC 发送给 render_service:
应用 A 提交 Transaction ──→ RecvRSTransactionData()
应用 B 提交 Transaction ──→ ↓
合并所有 Transaction
↓
SetDirtyFlag(true) ← 标记"这一帧有变更需要处理"
关键:只有提交了 Transaction 的应用才会在那一帧被处理。没有提交任何变更的应用(例如纯静态页面、动画暂停的窗口)不会被遍历,其已有 Buffer 不会释放,直接由 HWC 复用上次的合成结果。
4.2 阶段二:Dirty 标记传播
SetDirtyFlag() 只标记了顶层"有变更",实际需要逐节点判断哪些节点确实需要重绘。
脏区域 (Dirty Region): 每个 RSSurfaceRenderNode(对应一个应用窗口)内部维护该帧的脏区域:
RSSurfaceRenderNode
├── dirtyRegion_ ← 本帧需要重绘的区域(相对于自身坐标)
├── oldDirtyRegion_ ← 上一帧的脏区域
└── clearDirtyRegion_ ← 已处理的脏区域
Dirty 传播路径:
应用提交 Transaction
│
▼
RSSurfaceRenderNode::SetDirty() ← Surface 级别脏标记
│ 脏标记沿节点树向下传播
▼
RSCanvasRenderNode::SetDirty()
▼
RSRenderNode::SetContentDirty() ← 内容脏标记
▼
RSRenderNode::SetGeoDirty() ← 几何位置脏标记
引起 Dirty 的常见原因:
| 触发因素 | 示例 | 影响范围 |
|---|---|---|
| 节点属性变更 | 位置/大小/旋转/透明度变化 | 该节点 + 子节点 |
| Canvas 重绘 | 应用调用 Canvas API 绘制了新内容 | Surface 窗口 |
| 动画推进 | RSAnimation 每帧插值 | 动画作用的所有节点 |
| 新建节点 | 新窗口打开、新 UI 组件挂载 | 新建节点 |
| Modifier 变更 | 触摸反馈、焦点切换 | 相关节点 |
| 窗口滚动/滑动 | List/Grid/Scroll 滚动 | Surface 窗口 |
4.3 阶段三:可见性裁剪
即使节点是 Dirty 的,如果它被其他不透明窗口完全遮挡,render_service 也会跳过它的绘制,直接复用上一次的 Buffer。
遮挡剔除 (Occlusion Culling):
RSUniRenderVisitor::QuickPrepareSurfaceRenderNode()
│
├── 获取 Surface 的边界矩形
├── 检查是否被已处理的 Surface 遮挡
│ │
│ ▼
│ occlusionCullingRegion_ (累积遮挡区域)
│ Surface A 不透明且完全覆盖 Surface B → B 跳过绘制
│
├── 跳过绘制 ← Surface 被完全遮挡
└── 需要绘制 ← Surface 部分或完全可见
Z-order 在遮挡判断中的作用:
判断可见性时会考虑 Z-order,但方式不是显式比较两个节点的 Z 值高低——而是通过遍历顺序隐式实现。核心在 rs_uni_render_visitor.cpp 中:
遮挡剔除流程 (rs_uni_render_visitor.cpp):
1. Surface 节点按 Z-order 从高到低遍历(前→后)。
每处理一个节点 globalZOrder_++:
// L2578-2579
surfaceHandler->SetGlobalZOrder(... ? -1.f : globalZOrder_++);
2. 已处理的 Surface 的不透明区域累加到 accumulatedOcclusionRegion_:
// L1718
accumulatedOcclusionRegion_.OrSelf(node.GetOpaqueRegion());
3. 后遍历的 Surface 如果完全落在累积遮挡区域内,跳过 GPU 绘制。
因为 Z-order 更高的 Surface 已经先处理了,后遍历的相当于
"在更低 Z 层级且被遮挡"。
4. Z-order 变化会触发脏区域合并:
// L2989-2998
CheckMergeDisplayDirtyByZorderChanged(surfaceNode)
├── surfaceNode.GetZorderChanged() → 检测 Z-order 是否变化
└── curScreenNode_->GetDirtyManager()->MergeDirtyRect()
→ 将旧位置也标记为脏(避免残影)
| 文件 | 关键逻辑 |
|---|---|
rs_uni_render_visitor.cpp (L1110) |
QuickPrepareSurfaceRenderNode() 遍历 + Z-order 赋值 |
rs_uni_render_visitor.cpp (L2989) |
CheckMergeDisplayDirtyByZorderChanged() Z 变化检测 |
rs_uni_render_visitor.h (L780) |
globalZOrder_ 成员变量 |
rs_surface_render_node.h |
GetZorderChanged() / SetZorderChanged() |
rs_uni_dirty_compute_util.h/cpp |
脏区域计算 |
rs_occlusion_handler.h/cpp |
CalculateFrameOcclusion() / CollectNode() |
rs_uni_dirty_occlusion_util.h/cpp |
IsParticipateInOcclusion() 遮挡参与判断 |
一句话:遮挡剔除靠的是前→后遍历 + 累积遮挡区域,不是显式的 Z 值比较。Z-order 高的 Surface 先遍历,其不透明区域标记为"已遮挡",后面所有落在此区域的 Surface 都被跳过绘制。当 Z-order 发生变化时(如窗口切换),会触发脏区域合并确保新旧位置都正确更新。
可见性分类:
RSMainThread::Prepare() 遍历所有 Surface
│
├── 当前交互相应(前台)Surface
│ └── 始终需要绘制
│
├── 可见但非前台 Surface
│ └── 如果有 Dirty 标记,需要绘制
│
├── 不可见 Surface(被完全遮挡或最小化)
│ └── 跳过绘制,复用 Buffer
│
└── 有动画的 Surface
└── 即使被遮挡也保留 Dirty(动画需要持续更新)
4.4 完整的帧决策流程
VSync 到达
│
▼
RSMainThread::ProcessDataBySingleFrameComposer()
│ 接收所有应用在这一帧提交的 Transaction
│
▼
RSMainThread::SetDirtyFlag(true)
│ 只要有至少一个应用提交了变更,就标记有脏数据
│
▼
RSMainThread::Prepare() ← RSUniRenderVisitor
│ 遍历所有 DisplayNode → ScreenNode → SurfaceNode
│
├── 对每个 SurfaceNode:
│ ├── 有 Transaction 提交?→ 应用变更到节点
│ ├── 节点 Dirty?→ 加入渲染队列
│ ├── 节点不可见(被遮挡/最小化)?→ 跳过
│ ├── 节点是视频/SurfaceView?→ 标记为 HWC 层
│ └── 计算遮挡累积区域
│
▼
只有被标记为"需要绘制"的节点才进入 Render Thread
│
▼
Render Thread: GPU 绘制这些节点
│
▼
HWC Set: 将所有可见层(含跳过的 Buffer)提交合成
│
▼
Display Commit
4.5 后台应用的行为
后台应用不会收到 VSync 信号,也不需要提交帧数据。
应用侧:VSync 请求链
每个应用通过 RequestNextVSync() 向 appVSyncDistributor 注册接收 VSync。当应用进入后台时:
应用进入后台
│
▼
RSUIDirector::GoBackground()
│
├── isActive_ = false ← 标记非活跃
├── surfaceNode->MarkUIHidden(true) ← 标记 Surface 隐藏
├── surfaceNode->SetAbilityState(BACKGROUND) ← 设置后台状态
└── 停止主动 RequestNextVSync()
│
▼
不再接收 VSync → 不构建新帧 → 不提交 Transaction
「是否请求 VSync」取决于框架是否有待处理工作:
RSRenderThread::ProcessCommands()
│
├── 有待处理的命令(动画/状态更新)?
│ ├── ✅ → RequestNextVSync() → 收到 VSync → 渲染新帧
│ └── ❌ → 不请求 VSync → 不收到 VSync → 静默
│
└── 后台应用且无动画时,走到 ❌ 分支
→ CPU/GPU 零功耗,0 帧提交
渲染服务侧:即使收到也跳过
如果后台应用仍然提交了帧(例如残留动画),render_service 的处理如下:
render_service 收到后台应用的 Transaction
│
├── 标记 Dirty 标记
│
├── RSMainThread::Prepare()
│ └── RSUniRenderVisitor::QuickPrepareSurfaceRenderNode()
│ ├── surfaceNode 状态 = BACKGROUND?
│ ├── surfaceNode->IsUIHidden() = true?
│ ├── 被遮挡?
│ └── ✅ → 跳过 GPU 绘制,不清除 Buffer
│
└── Render Thread: 不处理该 Surface
└── HWC 复用时:该 Surface 的层标记为跳过
前台 vs 后台 vs 切换中的状态
| 状态 | 收到 VSync? | 提交帧? | GPU 绘制? | HWC 合成? |
|---|---|---|---|---|
| 前台可见 | ✅ 收到 | ✅ 提交 | ✅ 绘制 | ✅ 合成 |
| 前台被遮挡 | ✅ 收到 | ✅ 提交 | ❌ 跳过 | ✅ 复用 Buffer |
| 后台无动画 | ❌ 不接收 | ❌ 不提交 | N/A | 不参与 |
| 后台有动画 | ✅ 可能收到 | ✅ 可能提交 | ❌ 跳过 | ❌ 不参与 |
| 切换中 | ✅ 收到 | ✅ 提交 | ⚠️ 可能绘制 | ⚠️ 可能合成 |
关键结论:
- 后台无动画的应用:完全零开销——不接收 VSync,不构建帧,不提交 Transaction
- 被完全遮挡的前台应用:接收 VSync 构建帧,但 GPU 不绘制——Buffer 由 HWC 复用
- 动画不会因为应用进入后台而立刻停止——框架可能继续请求 VSync 直到动画完成,但 render_service 会跳过 GPU 绘制
| 场景 | 处理方式 |
|---|---|
| 没有提交 Transaction 的窗口 | 不触发重绘,HWC 复用上次 Buffer |
| 提交了但被完全遮挡的窗口 | 跳过 GPU 绘制,HWC 复用 Buffer |
| 有动画的窗口 | 即使被遮挡也保持 Dirty,持续绘制 |
| 全屏视频等硬件层 | 直接走 HWC,不经过 GPU |
5. 渲染服务:render_service 处理流程
5.1 三线程架构(概念模型)
render_service 在逻辑上分为三个处理阶段,但实际上只有 Render Thread 是独立线程,Main Thread 运行在 render_service 的主事件循环上,Hardware 阶段在 Render Thread 内同步执行:
| 阶段 | 线程模型 | 初始化时机 | 是否常驻 |
|---|---|---|---|
| Main Thread | render_service 主事件循环线程,RSMainThread::Instance() 静态单例 |
render_service 启动时,由外部传入 EventHandler 初始化 |
✅ 常驻 |
| Render Thread | 独立线程,RSUniRenderThread::Start() 创建 EventRunner("RSUniRenderThread") |
render_service 启动时显式调用 Start() |
✅ 常驻 |
| Hardware Thread | 概念阶段,无独立线程。HWC Prepare/Set + Display Commit 在 Render Thread 内同步执行 | — | N/A |
三者的协作关系:
render_service 进程启动
│
├── RSMainThread::Init(handler, vsyncReceiver, ...)
│ └── handler 绑定到 render_service 的主事件循环
│ 收到 VSync 信号后,handler 回调 mainLoop_
│
├── RSUniRenderThread::Instance().Start(composerClientManager)
│ └── new EventRunner("RSUniRenderThread") → 创建独立线程
│ 线程运行后初始化 GPU Context (InitGrContext)
│ 然后进入事件循环等待 PostTask()
│
└── [Hardware 处理] 在 Render Thread 内同步执行
└── RSUniRenderThread::RenderFrames()
├── GPU Composition (Skia → OpenGL/Vulkan)
├── HWC Prepare (查询硬件合成能力)
├── HWC Set (提交合成参数)
└── HDI/DRM Commit (帧提交到显示驱动)
Main Thread 初始化流程:
// RSMainThread.cpp:456
RSMainThread::RSMainThread()
: systemAnimatedScenesEnabled_(...), rsParallelType_(...) {
context_ = std::make_shared<RSContext>();
context_->Initialize();
}
// RSMainThread.cpp (Init 由外部调用)
void RSMainThread::Init(const std::shared_ptr<AppExecFwk::EventHandler>& handler,
const std::shared_ptr<VSyncReceiver>& receiver, ...) {
// handler 是 render_service 主线程的 EventHandler
// VSync 到达时 handler 调用 mainLoop_()
// mainLoop_ 中执行:ProcessCommand → Animate → Render → ...
}
Render Thread 初始化流程:
// RSUniRenderThread.cpp:240
void RSUniRenderThread::Start(const std::shared_ptr<RSComposerClientManager>&...) {
runner_ = AppExecFwk::EventRunner::Create("RSUniRenderThread"); // 创建独立线程
handler_ = std::make_shared<AppExecFwk::EventHandler>(runner_);
runner_->Run(); // 线程启动,进入事件循环
PostSyncTask([this] {
InitGrContext(); // 初始化 GPU 上下文
// ...
});
}
线程间同步:
Main Thread Render Thread
│ │
├── Prepare() │
├── 创建 stagingRenderThreadParams
│ │
└── PostAndWait() ───────────→ Sync(stagingParams)
│ (阻塞等待) │ 同步渲染参数
│ ├── GPU Render
│ ├── HWC Set
│ └── Display Commit
│ │
│ ←── UnblockMainThread() ────┘
│ (恢复) │
├── Commit() │
└── 申请下一个 VSync │
5.2 主线程 (Main Thread)
由 RSMainThread 管理,是渲染服务的入口:
VSync 回调
│
▼
RSMainThread::ProcessDataBySingleFrameComposer()
│ 处理从应用发来的所有 Transaction
▼
RSMainThread::Animate()
│ 推进动画(属性插值)
▼
RSMainThread::Prepare()
│ ┌─ RSUniRenderVisitor::PrepareRootNode()
│ │ 遍历节点树,计算可见性、脏区域
│ │ 构建 Drawable 列表(将 RSNode → Drawable)
│ │ 应用裁剪、遮挡剔除
│ └─ 输出:stagingRenderThreadParams
│
▼
RSMainThread::Process()
│ ┌─ 处理离屏渲染、EffectNode
│ └─ 准备渲染目标 Buffer
│
▼
PostAndWait() ← 向渲染线程提交任务,同步等待完成
│
▼
RSMainThread::Commit()
│ ┌─ 通知应用侧:"帧已消费,可以开始下一帧"
│ └─ 申请下一个 VSync
关键处理流程:
一帧 (Main Thread):
ProcessTransaction → Animate → Prepare → Process → PostAndWait → Commit
↑
构建 Drawable
5.3 渲染线程 (Render Thread)
由 RSUniRenderThread 管理,负责实际的 GPU 绘制:
收到 PostAndWait() 请求
│
▼
RSDrawFrame::Sync()
│ 同步 RSRenderThreadParams(获取主线程传入的渲染参数)
│ 准备绘制上下文(GPU Context)
│ 建立渲染目标(Buffer)
▼
RSDrawFrame::Render()
│ ┌─ 遍历 Drawable 列表
│ │ ├── 不透明层 → 从后向前绘制
│ │ ├── 透明层 → 混合绘制(Alpha Blending)
│ │ └── 特效 → Blur、Shadow、ColorFilter
│ │
│ ├─ GPU Composition
│ │ 如果各图层需要合并 → 用 Skia/OpenGL 合成最终帧
│ │ 如果 HWC 支持直接合成 → 跳过 GPU 合成,交给 HWC
│ │
│ ├─ HWC 处理
│ │ 调用 Hardware Composer 接口
│ │ 将各图层信息传递给 HWC Driver
│ │
│ └─ Submit
│ 提交 GPU command buffer / 标记 HWC 图层
│
▼
RSDrawFrame::UnblockMainThread()
│ 通知主线程渲染完成
▼
等待下一帧 VSync
5.4 硬件处理阶段 (HWC/Display)
负责 HWC 和显示提交:
┌─ HWC Prepare
│ HWC 硬件驱动计算合成策略
│ 返回:哪些图层可以用 HWC 合成
│ 哪些需要 GPU 合成
│
├─ GPU Composition (如果需要)
│ 对 HWC 不支持的图层用 GPU 合成
│
├─ HWC Set
│ 提交最终合成参数给 HWC Driver
│
└─ Display Commit
通过 HDI (Hardware Display Interface) 提交帧
送到显示驱动 → 屏幕
6. 合成策略:GPU vs HWC
| 维度 | GPU Composition | HWC (Hardware Composer) |
|---|---|---|
| 执行位置 | GPU (Skia/OpenGL/Vulkan) | 显示硬件控制器 |
| 功耗 | 较高(GPU 占用) | 极低(专用硬件) |
| 灵活性 | 任意特效(模糊、滤镜、变形) | 仅基础合成(叠加、旋转、缩放) |
| 适用场景 | 复杂 UI 效果 | 全屏视频、静态图层叠加 |
| 决策 | render_service 动态选择 | HWC driver 报告能力 |
6.1 合成决策流程
渲染线程在VSync到达后依次判断:
渲染线程 Render()
│
├── 检查各图层的属性
│ ├── 不重叠 → 可 HWC 合成
│ ├── 有特效 → 必须 GPU 合成
│ └── 视频层 → 优先 HWC(省电)
│
├── 调用 HWC Prepare()
│ HWC 报告:"这些层我能合成,其他层你自己画"
│
├── GPU 合成剩余图层
│
└── HWC Set() + Commit
7. 上屏:帧提交到显示器
7.1 图形栈全链路
应用进程
│ ArkUI 构建 UI 树 → Canvas 绘制指令
│
├── [进程内] RenderServiceClient NAPI
│ RSNode 操作 → 序列化为 Transaction
│
├── [IPC] Transaction → render_service
│
▼
render_service 进程
│
├── [主线程] 解析 Transaction → 更新节点树
├── [主线程] 构建 Drawable
├── [渲染线程] Skia 绘制/GPU Composition
├── [渲染线程] HWC Prepare/Set
│
▼
HDI (Hardware Display Interface)
│ OpenHarmony 显示硬件抽象层
│
├── DisplayEngine
│ 驱动适配层 (HDF)
│
▼
Linux DRM/KMS
│
├── DRM (Direct Rendering Manager)
│ ├── framebuffer 管理
│ ├── CRTC 配置
│ ├── encoder/connector 管理
│ └── page flip 提交
│
▼
显示硬件
屏幕扫描输出
7.2 上屏时序
VSync (t=0ms)
│
├── Main Thread 开始处理 (0-4ms)
│ ProcessTransaction → Animate → Prepare → Process
│
├── Render Thread 开始 (4-10ms)
│ Sync → GPU Render → HWC Set
│
├── Display Commit (10-12ms)
│ 通过 HDI/DRM 提交到显示驱动
│
└── 屏幕在下一个 VSync (t=16.6ms) 显示完成
↑ ↑ ↑
VSync N VSync N+1 VSync N+2
│ │ │
├── 帧 N 开始处理 ├── 帧 N 上屏 ├── 帧 N+1 上屏
└── └── 帧 N+1 开始处理 └──
8. 与 Android 对比
| 维度 | OHOS (Rosen) | Android (SurfaceFlinger) |
|---|---|---|
| 渲染服务 | render_service (rosen) |
SurfaceFlinger |
| 合成框架 | Rosen (自有实现) | SurfaceFlinger + HWC |
| GPU 引擎 | Skia + OpenGL/Vulkan | Skia + OpenGL ES/Vulkan |
| 节点系统 | RSNode 体系 | Surface/Layer 体系 |
| VSync 管理 | 应用/渲染服务双通道 | Choreographer + SF VSync |
| IPC | 自定义 Transaction 协议 | Binder + BufferQueue |
| 合成策略 | GPU + HWC 混合 | CPU + GPU + HWC 混合 |
| 硬件抽象 | HDI (HDI Display) | HWC HAL (Hardware Composer HAL) |
| 显示提交 | DRM/KMS page flip | DRM/KMS / HWC |
| 客户端库 | render_service_client |
libgui (Surface/BufferQueue) |
| 动画系统 | RSAnimation + Modifier | PropertyAnimation + Choreographer |
| 三线程 | Main + Render (Hardware 在 Render 内) | Main + Render + HWC |
9. 关键技术点
9.1 脏区域 (Dirty Region)
只重新绘制上一帧以来发生变化的区域,而非全屏重绘。RSUniRenderVisitor::PrepareRootNode() 中计算每一帧的脏区域,通过相邻脏区域合并减少绘制次数。
9.2 遮挡剔除 (Occlusion Culling)
通过 stack_culling 特性,跳过被其他不透明窗口完全遮挡的节点的绘制,节省 GPU 带宽。遮挡判断基于前→后遍历顺序和累积遮挡区域运算,详见 4.3 节。
9.3 动态帧率 (HGM)
HgmCore 根据屏幕内容和功耗策略动态调整刷新率(60/90/120Hz),在低负载场景(静态页面)降低帧率以省电。HGM 通过 VSyncSampler 实时采样 VSync 信号,结合 HgmFrameRateManager 调整两条 VSync 通道的帧率。
9.4 并行渲染
通过 parallel_render 特性,将不相交的图层分配到不同 GPU 线程并行渲染,再合成最终帧。由 RSSubThreadManager 管理子线程的任务分发和结果收集。
9.5 内存优化
| 优化手段 | 说明 | 实现 |
|---|---|---|
| GPU 缓存管理 | GPUCacheManager 管理 GPU 纹理缓存,避免重复创建 |
render_service/core/memory/ |
| AFBC | ARM Frame Buffer Compression,压缩显存带宽 | 硬件特性,在 Buffer 分配时启用 |
| 离屏 Buffer 复用 | 减少 Buffer 分配开销,复用上一帧离屏渲染结果 | RSRenderNode::CachedOp |
| 内存回收 | ClearMemoryCache() 在低内存时刻清理不用的 GPU 资源 |
RSMainThread::ClearMemoryCache() |

浙公网安备 33010602011771号