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()
posted @ 2026-05-28 10:57  getmoon  阅读(91)  评论(0)    收藏  举报