1. 课程导入
移动图形开发与桌面图形开发最大的差异,不在于 API 语法,而在于资源约束。手机和平板受限于功耗、散热、内存带宽与电池容量,GPU 的峰值算力往往无法长时间维持。因此,OpenGL ES 性能优化工程并不是“把代码写得更快”这么简单,它是一门围绕帧预算、带宽、功耗与渲染正确性进行系统权衡的工程学科。本课程的目标,是让你从“能画出画面”进阶到“能稳定、低耗、可预测地画出画面”。
一、核心概念:帧预算、带宽与管线
移动图形性能的第一性原理是帧预算。以 60 FPS 为例,每帧只有约 16.6 ms;若目标为 120 FPS,则压缩到约 8.3 ms。这 16.6 ms 需要同时容纳 CPU 逻辑、驱动提交、GPU 顶点处理、光栅化、片元着色、合成与显示。任何一环超支,都会表现为掉帧、卡顿或发热降频。
第二个核心概念是带宽。移动 GPU 通常采用 Tile-Based Rendering(TBR)架构,把屏幕划分为小块,在片上高速缓存中完成片元处理,从而减少对系统内存的访问。但一旦出现频繁的 Render Target 切换、全屏后处理、大纹理采样或每帧重新上传顶点数据,带宽就会迅速成为瓶颈。很多“GPU 占用不高却依然卡”的现象,本质是带宽或同步等待造成的。
第三个核心概念是渲染管线。OpenGL ES 的管线从顶点着色器、图元装配、光栅化、片元着色器到逐片元操作,每一阶段都有对应的优化手段:顶点阶段关注属性数量与矩阵计算,片元阶段关注纹理采样次数与分支,逐片元阶段关注混合、深度测试与 Early-Z 的失效条件。
二、整体框架:从测量到验证的闭环
性能优化最忌讳“凭感觉改代码”。工程化的做法是建立闭环:测量 → 定位 → 假设 → 修改 → 验证 → 固化基线。测量阶段要区分 CPU 时间与 GPU 时间,区分渲染线程与逻辑线程;定位阶段要借助抓帧工具查看每个 Draw Call 的状态、纹理绑定与着色器指令;验证阶段必须用同一场景、同一设备、同一分辨率做 A/B 对比,否则优化结论不可信。
下面给出一个典型的优化工作流清单,可作为项目中的检查表:
| 阶段 | 关键动作 | 常用指标 | 常见产出 |
|---|---|---|---|
| 基线测量 | 固定场景与设备,采集帧时间 | FPS、帧时间分布、GPU 时间 | 性能基线报告 |
| 瓶颈定位 | 抓帧分析 Draw Call 与状态 | Draw Call 数、Overdraw、带宽 | 热点列表 |
| 方案实施 | 批处理、纹理压缩、Shader 简化 | CPU/GPU 时间变化 | 优化补丁 |
| 回归验证 | 多机型对比,检查画质与功耗 | 帧率稳定性、温度、耗电 | 验收结论 |
三、案例场景:一个典型的“卡顿”排查
假设某 3D 场景在中端机上只有 35 FPS,且机身明显发热。初步抓帧发现:Draw Call 约 420 次,每帧有 3 次 Render Target 切换,片元着色器中有动态分支与多次纹理采样。此时不要急于重写着色器,而应先判断瓶颈类型。若 GPU 时间远高于 CPU 时间,优先处理 Overdraw 与纹理采样;若 CPU 提交时间占比高,则优先做批处理与状态排序。
// 伪代码:用时间戳粗略区分 CPU 与 GPU 耗时
startCpu = now();
issueDrawCalls();
endCpu = now();
gpuBegin = queryGpuTimestampBegin();
renderFrame();
gpuEnd = queryGpuTimestampEnd();
cpuMs = endCpu - startCpu;
gpuMs = gpuEnd - gpuBegin;
// 若 cpuMs 接近帧预算,优先优化提交与状态;
// 若 gpuMs 接近帧预算,优先优化着色与带宽。
四、误区与复盘
- 误区一:只看平均 FPS。平均帧率会掩盖卡顿,应关注帧时间的高百分位(如 95th、99th)。
- 误区二:把 GPU 占用低等同于没有 GPU 瓶颈。带宽、同步与驱动提交同样会造成等待。
- 误区三:一次性改太多变量。无法归因的优化不可复用,应单变量验证。
- 误区四:忽略功耗与温控。短时间跑分高,长时间降频后体验反而更差。
本课程后续将围绕这条主线展开:先建立测量与定位能力,再分别深入 CPU 提交、GPU 几何、片元与带宽、纹理与内存、Shader 与工具链,最终形成一套可落地的移动图形性能优化工程方法。掌握这套方法后,你面对的不再是“玄学卡顿”,而是一组可测量、可解释、可验证的工程问题。