HarmonyOS开发:图形管线与渲染流程全解析
HarmonyOS开发:图形管线与渲染流程全解析
📌 核心要点:深入理解HarmonyOS从应用层绘制指令到屏幕像素点亮的完整渲染管线,掌握RenderService、BufferQueue、VSync三大核心机制,让你的应用渲染性能从"能用"飞跃到"丝滑"。
一、背景与动机
你有没有想过,当你在代码里写下 Text('Hello HarmonyOS') 这行看似简单的代码后,屏幕上那几个漂亮的文字到底经历了怎样的旅程才最终呈现在你眼前?
说实话,很多开发者对渲染管线的认知停留在"写UI → 看到效果"这个黑盒层面。但当你遇到以下场景时,这个黑盒就会变成噩梦:
- 列表滑动时莫名卡顿,却不知道瓶颈在CPU还是GPU
- 动画帧率忽高忽低,找不到掉帧的根源
- 多窗口场景下画面撕裂,束手无策
- 复杂界面渲染延迟严重,却不知道从哪里优化
这就像开车不懂发动机原理——平时没问题,一旦抛锚就只能干瞪眼。理解图形渲染管线,就是给你的开发技能装上"X光眼",让你能透视整个渲染链路,精准定位性能瓶颈。
HarmonyOS的图形渲染管线并非简单照搬Android的SurfaceFlinger,它从架构层面做了大量创新设计,尤其是RenderService的统一渲染服务、BufferQueue的生产者-消费者模型、以及基于VSync的帧调度机制,这三者构成了整个渲染管线的骨架。
二、核心原理
2.1 HarmonyOS图形渲染管线全景架构
HarmonyOS的图形渲染管线可以概括为"三层四段"架构——三层是指应用层、框架服务层、硬件层;四段是指构建、渲染、合成、显示四个阶段。
graph TD
A[应用层 ArkUI组件树]:::primary --> B[构建阶段: 组件树→渲染树]:::info
B --> C[渲染阶段: RenderService绘制]:::warning
C --> D[合成阶段: 合成器图层合成]:::warning
D --> E[显示阶段: 屏幕呈现]:::success
F[BufferQueue生产者]:::error -.-> C
G[BufferQueue消费者]:::error -.-> D
H[VSync信号]:::info -.-> C
H -.-> D
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef success fill:#9C27B0,stroke:#7B1FA2,color:#fff
让我们逐层拆解这个架构。
2.2 构建阶段:从组件树到渲染树
当你在ArkTS中声明UI组件时,HarmonyOS会经历以下转换:
- 组件树构建:ArkUI框架解析你的声明式代码,构建出组件树(Component Tree)
- 渲染树生成:组件树经过布局计算(Measure → Layout),生成渲染树(Render Tree)
- 绘制指令收集:渲染树遍历生成DisplayList,即一系列绘制指令的集合
这个过程就像建筑师画图纸——组件树是概念设计图,渲染树是施工图,DisplayList则是具体的施工指令清单。
2.3 RenderService:统一渲染服务
RenderService是HarmonyOS图形子系统的核心服务进程,它承担了三大职责:
- 渲染调度:接收应用的绘制请求,协调GPU执行渲染任务
- 图层合成:将多个窗口/图层的渲染结果合成为最终帧
- VSync分发:向各应用进程分发垂直同步信号,驱动帧渲染节奏
graph LR
A[应用进程A]:::primary --> RS[RenderService]:::warning
B[应用进程B]:::primary --> RS
C[系统UI进程]:::primary --> RS
RS --> COMP[合成器]:::info
COMP --> SCREEN[屏幕]:::success
VSYNC[VSync硬件信号]:::error --> RS
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef success fill:#9C27B0,stroke:#7B1FA2,color:#fff
RenderService的设计哲学是"统一管理、按需渲染"。与Android每个应用独立渲染不同,HarmonyOS将渲染决策权收归RenderService,这样可以全局优化GPU资源分配,避免多个应用争抢GPU导致的性能抖动。
2.4 BufferQueue:生产者-消费者模型
BufferQueue是渲染管线中连接"渲染"和"合成"的桥梁,它采用经典的生产者-消费者模型:
- 生产者(Producer):应用进程的渲染线程,将绘制结果写入Buffer
- 消费者(Consumer):RenderService的合成线程,从Buffer读取内容进行合成
graph TD
P1[渲染线程1]:::primary --> BQ[BufferQueue]:::info
P2[渲染线程2]:::primary --> BQ
BQ --> C1[合成线程]:::warning
BQ --> C2[截图服务]:::warning
BQ -.-> |空闲Buffer回环| P1
BQ -.-> |空闲Buffer回环| P2
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
BufferQueue通常维护3个Buffer(三缓冲机制):
| Buffer状态 | 说明 |
|---|---|
| FREE | 空闲,可被生产者获取 |
| DEQUEUED | 已被生产者取出,正在写入 |
| QUEUED | 已写入完成,等待消费者读取 |
| ACQUIRED | 已被消费者取出,正在使用 |
三缓冲的意义在于:当GPU正在渲染当前帧时,CPU可以提前准备下一帧的数据,避免CPU等待GPU造成的流水线气泡。
2.5 VSync信号与帧调度
VSync(垂直同步)信号是整个渲染管线的"心跳"。屏幕以固定频率(通常60Hz或120Hz)发出VSync信号,RenderService收到信号后触发一帧的渲染流程。
graph TD
VSYNC[VSync信号到来]:::error --> A[唤醒应用进程]:::primary
A --> B[执行动画/输入处理]:::info
B --> C[构建渲染树]:::info
C --> D[生成DisplayList]:::info
D --> E[提交给RenderService]:::warning
E --> F[GPU执行渲染]:::warning
F --> G[合成并上屏]:::success
G -.-> |下一帧| VSYNC
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef success fill:#9C27B0,stroke:#7B1FA2,color:#fff
帧调度的核心目标是:在两个VSync信号之间的间隔(16.67ms@60Hz)内完成"输入处理→动画计算→布局→渲染→合成"全流程。任何一个环节超时,都会导致掉帧。
HarmonyOS引入了VSync预测模型,它不是简单地等待硬件VSync信号,而是基于历史数据预测下一个VSync的到来时间,提前启动渲染流程,从而给渲染留出更多时间裕量。
三、代码实战
3.1 基础用法:监控渲染帧率
理解渲染管线的第一步,是学会监控帧率。HarmonyOS提供了fpsMonitor模块来实时监控应用的渲染性能:
import { fpsMonitor } from '@kit.PerformanceAnalysisKit';
@Entry
@Component
struct FrameMonitorDemo {
// 帧率数据
@State currentFps: number = 0;
@State droppedFrames: number = 0;
private fpsCollector: fpsMonitor.FpsMonitor | null = null;
aboutToAppear() {
// 创建帧率监控器
this.fpsCollector = fpsMonitor.createFpsMonitor();
// 注册帧率回调
this.fpsCollector.on('fpsChange', (fps: number) => {
this.currentFps = Math.round(fps);
});
// 注册掉帧回调
this.fpsCollector.on('jankFrame', (jankInfo: fpsMonitor.JankFrameInfo) => {
this.droppedFrames++;
console.info(`掉帧详情 - 期望帧时间: ${jankInfo.expectedFrameTime}ms, ` +
`实际帧时间: ${jankInfo.actualFrameTime}ms`);
});
// 启动监控
this.fpsCollector.start();
}
aboutToDisappear() {
// 停止监控并释放资源
this.fpsCollector?.stop();
this.fpsCollector = null;
}
build() {
Column() {
Text(`当前帧率: ${this.currentFps} FPS`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(`累计掉帧: ${this.droppedFrames}`)
.fontSize(18)
.fontColor(this.droppedFrames > 10 ? Color.Red : Color.Green)
.margin({ top: 10 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
3.2 进阶用法:VSync回调与帧调度
在需要精确控制动画节奏的场景中,我们可以直接使用VSync回调来驱动渲染:
import { renderingSync } from '@kit.ArkGraphics2D';
@Entry
@Component
struct VSyncDrivenAnimation {
@State translateX: number = 0;
@State translateY: number = 0;
private vsyncReceiver: renderingSync.VSyncReceiver | null = null;
private animationRunning: boolean = false;
private velocityX: number = 3;
private velocityY: number = 2;
private lastTimestamp: number = 0;
aboutToAppear() {
// 创建VSync接收器
this.vsyncReceiver = new renderingSync.VSyncReceiver();
}
// 启动VSync驱动的动画
startVSyncAnimation() {
if (this.animationRunning) return;
this.animationRunning = true;
this.lastTimestamp = 0;
// 请求下一次VSync信号
this.requestVSync();
}
// 停止动画
stopVSyncAnimation() {
this.animationRunning = false;
}
private requestVSync() {
if (!this.animationRunning || !this.vsyncReceiver) return;
this.vsyncReceiver.requestVSync((timestamp: number) => {
if (this.lastTimestamp === 0) {
this.lastTimestamp = timestamp;
}
// 计算帧间隔(纳秒转毫秒)
const deltaTime = (timestamp - this.lastTimestamp) / 1_000_000;
this.lastTimestamp = timestamp;
// 基于真实时间差更新位置,确保动画速度一致
const factor = deltaTime / 16.67; // 归一化到60FPS
this.translateX += this.velocityX * factor;
this.translateY += this.velocityY * factor;
// 边界反弹
if (this.translateX > 300 || this.translateX < 0) {
this.velocityX = -this.velocityX;
}
if (this.translateY > 300 || this.translateY < 0) {
this.velocityY = -this.velocityY;
}
// 继续请求下一帧
this.requestVSync();
});
}
build() {
Column() {
// 动画目标
Row() {
Circle()
.width(40)
.height(40)
.fill('#FF6B35')
}
.width(340)
.height(340)
.border({ width: 2, color: '#E0E0E0', radius: 8 })
// 控制按钮
Row() {
Button('启动动画')
.onClick(() => this.startVSyncAnimation())
.backgroundColor('#4CAF50')
Button('停止动画')
.onClick(() => this.stopVSyncAnimation())
.backgroundColor('#F44336')
.margin({ left: 20 })
}
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(20)
}
}
3.3 完整示例:渲染管线性能分析器
下面是一个完整的渲染性能分析工具,它集成了帧率监控、掉帧检测、渲染耗时追踪等功能:
import { fpsMonitor } from '@kit.PerformanceAnalysisKit';
// 渲染帧数据结构
interface FrameData {
timestamp: number; // 帧时间戳
fps: number; // 帧率
renderTime: number; // 渲染耗时
isJank: boolean; // 是否掉帧
jankSeverity: 'none' | 'minor' | 'major'; // 掉帧严重程度
}
@Entry
@Component
struct RenderPipelineAnalyzer {
// 状态数据
@State currentFps: number = 0;
@State avgFps: number = 0;
@State totalJanks: number = 0;
@State majorJanks: number = 0;
@State isMonitoring: boolean = false;
@State fpsHistory: number[] = [];
@State renderTimeMs: number = 0;
// 内部数据
private fpsMonitorInstance: fpsMonitor.FpsMonitor | null = null;
private frameDataList: FrameData[] = [];
private maxHistoryLength: number = 60;
private startTime: number = 0;
aboutToAppear() {
this.fpsMonitorInstance = fpsMonitor.createFpsMonitor();
}
// 开始监控
startMonitoring() {
this.isMonitoring = true;
this.startTime = Date.now();
this.frameDataList = [];
this.totalJanks = 0;
this.majorJanks = 0;
this.fpsMonitorInstance?.on('fpsChange', (fps: number) => {
this.currentFps = Math.round(fps);
this.updateFpsHistory(fps);
});
this.fpsMonitorInstance?.on('jankFrame', (jankInfo: fpsMonitor.JankFrameInfo) => {
this.totalJanks++;
const severity = jankInfo.actualFrameTime > 50 ? 'major' : 'minor';
if (severity === 'major') this.majorJanks++;
this.frameDataList.push({
timestamp: Date.now(),
fps: this.currentFps,
renderTime: jankInfo.actualFrameTime,
isJank: true,
jankSeverity: severity
});
});
this.fpsMonitorInstance?.start();
}
// 停止监控
stopMonitoring() {
this.isMonitoring = false;
this.fpsMonitorInstance?.stop();
this.calculateSummary();
}
// 更新帧率历史
private updateFpsHistory(fps: number) {
this.fpsHistory.push(Math.round(fps));
if (this.fpsHistory.length > this.maxHistoryLength) {
this.fpsHistory.shift();
}
}
// 计算汇总数据
private calculateSummary() {
if (this.fpsHistory.length === 0) return;
const sum = this.fpsHistory.reduce((acc, val) => acc + val, 0);
this.avgFps = Math.round(sum / this.fpsHistory.length);
}
// 生成分析报告
generateReport(): string {
const duration = ((Date.now() - this.startTime) / 1000).toFixed(1);
const jankRate = this.frameDataList.length > 0
? ((this.totalJanks / this.frameDataList.length) * 100).toFixed(1)
: '0.0';
return `渲染管线分析报告\n` +
`━━━━━━━━━━━━━━━━━━\n` +
`监控时长: ${duration}s\n` +
`平均帧率: ${this.avgFps} FPS\n` +
`当前帧率: ${this.currentFps} FPS\n` +
`掉帧次数: ${this.totalJanks} (严重: ${this.majorJanks})\n` +
`掉帧率: ${jankRate}%\n` +
`━━━━━━━━━━━━━━━━━━\n` +
`诊断建议:\n` +
(this.avgFps < 55 ? '⚠️ 平均帧率偏低,检查布局复杂度\n' : '') +
(this.majorJanks > 5 ? '🔴 严重掉帧过多,检查GPU负载\n' : '') +
(this.avgFps >= 55 && this.majorJanks <= 2 ? '✅ 渲染性能良好\n' : '');
}
build() {
Scroll() {
Column() {
// 标题区
Text('渲染管线分析器')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 实时数据卡片
Row() {
this.StatCard('当前FPS', `${this.currentFps}`, this.currentFps >= 55 ? '#4CAF50' : '#F44336')
this.StatCard('平均FPS', `${this.avgFps}`, this.avgFps >= 55 ? '#4CAF50' : '#FF9800')
this.StatCard('掉帧', `${this.totalJanks}`, this.totalJanks < 5 ? '#4CAF50' : '#F44336')
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ bottom: 20 })
// 帧率历史可视化(简易柱状图)
Column() {
Text('帧率趋势')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 8 })
Row() {
ForEach(this.fpsHistory.slice(-30), (fps: number, index: number) => {
Column() {
// 用高度表示帧率
Column()
.width(6)
.height(`${(fps / 120) * 100}%`)
.backgroundColor(fps >= 55 ? '#4CAF50' : fps >= 30 ? '#FF9800' : '#F44336')
.borderRadius(2)
}
.width(8)
.height(80)
.justifyContent(FlexAlign.End)
}, (fps: number, index: number) => `${index}_${fps}`)
}
.width('100%')
.height(80)
.alignItems(VerticalAlign.Bottom)
}
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ bottom: 20 })
// 控制按钮
Row() {
Button(this.isMonitoring ? '停止监控' : '开始监控')
.onClick(() => {
if (this.isMonitoring) {
this.stopMonitoring();
} else {
this.startMonitoring();
}
})
.backgroundColor(this.isMonitoring ? '#F44336' : '#4CAF50')
.width(160)
}
.margin({ bottom: 20 })
// 分析报告
if (!this.isMonitoring && this.frameDataList.length > 0) {
Column() {
Text('分析报告')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Text(this.generateReport())
.fontSize(14)
.fontFamily('monospace')
.lineHeight(22)
}
.width('100%')
.padding(16)
.backgroundColor('#FFF8E1')
.borderRadius(12)
}
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
}
// 统计卡片组件
@Builder
StatCard(title: string, value: string, color: string) {
Column() {
Text(title)
.fontSize(12)
.fontColor('#666666')
Text(value)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(color)
.margin({ top: 4 })
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
}
}
四、踩坑与注意事项
坑点1:BufferQueue的Buffer泄漏
在自定义渲染场景中(如XComponent),如果生产者Dequeue了Buffer但忘记Queue回去,会导致BufferQueue耗尽可用Buffer,后续渲染请求被阻塞,画面卡死。务必在异常路径中也确保Buffer被归还。
坑点2:VSync回调中的重操作
VSync回调是在UI线程执行的,如果在回调中执行耗时操作(如文件I/O、网络请求),会直接阻塞下一帧的渲染,导致连续掉帧。VSync回调中只应做轻量的状态更新和动画计算。
坑点3:三缓冲的内存开销
三缓冲意味着每个Surface需要3个GraphicBuffer,对于4K分辨率+HDR的Surface,单个Buffer可能占用32MB以上,三个就是近100MB。在多窗口场景下,Buffer的内存开销不容忽视。建议非活跃窗口主动释放Buffer。
坑点4:RenderService的进程隔离
RenderService运行在独立进程中,应用进程与RenderService之间通过IPC通信。这意味着每次提交渲染数据都涉及跨进程数据传输,对于超大DisplayList,IPC的开销可能成为瓶颈。优化方向是减少DisplayList的指令数量,使用缓存和复用策略。
坑点5:VSync预测模型的冷启动
VSync预测模型需要一定数量的历史信号来建立预测,应用冷启动时预测精度较低,可能导致首帧渲染时机不理想,表现为冷启动时的短暂卡顿。可以通过预热VSync接收器来缓解。
坑点6:GPU渲染与CPU渲染的切换陷阱
某些组件(如带有复杂ShaderEffect的组件)可能触发GPU渲染回退到CPU渲染,这种回退是静默发生的,不会报错,但性能会断崖式下降。建议在开发阶段开启GPU渲染监控,及时发现回退。
坑点7:多窗口合成时的Z序问题
在多窗口叠加场景中,如果两个窗口的Z序频繁变化,RenderService需要频繁重新合成,这会导致GPU负载飙升。建议合理规划窗口层级,减少Z序变化。
五、HarmonyOS 6适配说明
API差异
| API | HarmonyOS 5.0 | HarmonyOS 6.0 | 迁移建议 |
|---|---|---|---|
| fpsMonitor.createFpsMonitor() | 返回FpsMonitor实例 | 返回FpsMonitor实例,新增config参数 | 使用新config参数指定监控粒度 |
| VSyncReceiver.constructor() | 无参数 | 新增name参数用于标识接收器 | 传入接收器名称便于调试 |
| BufferQueue.setQueueSize() | 支持2-8个Buffer | 支持1-16个Buffer,新增内存预算模式 | 使用内存预算模式自动调整Buffer数量 |
| RenderNode.draw() | 同步绘制 | 支持异步绘制回调 | 使用异步模式避免阻塞UI线程 |
| renderingSync.requestVSync() | 单次请求 | 支持批量VSync请求 | 合并多个VSync请求减少IPC开销 |
行为变更
- VSync信号频率自适应:HarmonyOS 6.0中,VSync信号频率不再固定,而是根据屏幕刷新率和应用需求动态调整(LTPO屏幕支持1-120Hz自适应),应用需要适配可变帧率
- BufferQueue默认Buffer数量变更:从3个调整为根据屏幕分辨率和可用内存动态计算,高分辨率设备可能默认4个Buffer
- RenderService合成策略优化:脏区域(Dirty Region)合成为默认行为,只有发生变化的区域才会重新合成,应用需要正确标记脏区域
- GPU渲染回退通知机制:当GPU渲染回退到CPU渲染时,6.0会通过回调通知应用,应用可以据此优化渲染策略
适配代码
import { fpsMonitor } from '@kit.PerformanceAnalysisKit';
import { renderingSync } from '@kit.ArkGraphics2D';
@Entry
@Component
struct HarmonyOS6RenderAdapter {
@State currentFps: number = 0;
@State isGpuRendering: boolean = true;
private fpsMonitorInstance: fpsMonitor.FpsMonitor | null = null;
private vsyncReceiver: renderingSync.VSyncReceiver | null = null;
aboutToAppear() {
// HarmonyOS 6.0: 使用config参数创建帧率监控器
this.fpsMonitorInstance = fpsMonitor.createFpsMonitor({
granularity: fpsMonitor.Granularity.GRANULARITY_PER_FRAME, // 逐帧粒度监控
enableGpuFallbackDetection: true // 启用GPU回退检测(6.0新增)
});
// HarmonyOS 6.0: 创建带名称标识的VSync接收器
this.vsyncReceiver = new renderingSync.VSyncReceiver('MainAnimation');
// 监听GPU渲染回退(6.0新增)
this.fpsMonitorInstance?.on('renderModeChange', (mode: fpsMonitor.RenderMode) => {
this.isGpuRendering = (mode === fpsMonitor.RenderMode.GPU);
if (!this.isGpuRendering) {
console.warn('⚠️ GPU渲染已回退到CPU渲染,请检查组件配置');
}
});
this.fpsMonitorInstance?.on('fpsChange', (fps: number) => {
this.currentFps = Math.round(fps);
});
this.fpsMonitorInstance?.start();
}
// HarmonyOS 6.0: 适配可变帧率的VSync请求
requestAdaptiveVSync() {
// 批量请求VSync(6.0新增),减少IPC开销
this.vsyncReceiver?.requestVSync((timestamp: number) => {
// 根据实际VSync间隔动态调整动画步进
this.handleVSyncCallback(timestamp);
}, {
preferredInterval: 8_333_333, // 期望120Hz(纳秒)
tolerance: 0.2 // 允许20%的偏差
});
}
private handleVSyncCallback(timestamp: number) {
// 动画逻辑...
}
build() {
Column() {
Text(`FPS: ${this.currentFps}`)
.fontSize(24)
// GPU渲染状态指示
Row() {
Circle()
.width(12)
.height(12)
.fill(this.isGpuRendering ? '#4CAF50' : '#F44336')
Text(this.isGpuRendering ? 'GPU渲染' : 'CPU渲染(已回退)')
.fontSize(14)
.fontColor(this.isGpuRendering ? '#4CAF50' : '#F44336')
.margin({ left: 8 })
}
.margin({ top: 10 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
六、总结
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ |
| 使用频率 | ⭐⭐⭐⭐ |
| 重要程度 | ⭐⭐⭐⭐⭐ |
图形渲染管线是HarmonyOS图形子系统的"大动脉",理解它不仅仅是面试加分项,更是解决实际渲染问题的必备知识。从RenderService的统一调度,到BufferQueue的生产者-消费者模型,再到VSync信号驱动帧节奏,这三者环环相扣,构成了一个精密的渲染流水线。
记住:当你遇到渲染性能问题时,不要盲目优化,先定位瓶颈在管线的哪个阶段——是构建太慢(CPU瓶颈),还是渲染太慢(GPU瓶颈),还是合成太慢(带宽瓶颈),对症下药才能事半功倍。
如果你对渲染管线的某个环节还想深入了解,比如GPU渲染的具体实现或者自定义渲染引擎的搭建,后续文章我们会继续展开。保持好奇心,我们下篇见!
- 点赞
- 收藏
- 关注作者
评论(0)