2. 嵌入式GUI基础

做嵌入式GUI优化,最怕什么?

不是代码写不出来,而是界面卡了你却不知道卡在哪。我见过太多团队,一卡就怀疑CPU,一顿猛优化算法,结果发现瓶颈在GPU。白忙活。

所以这一章,咱们把渲染管线彻底拆开看。CPU、GPU、合成器,各自在干什么?怎么快速定位谁在拖后腿?

渲染管线的三个核心环节

一个画面从代码到屏幕,大致走三步:

  1. CPU阶段:处理输入、更新逻辑、构建绘制指令
  2. GPU阶段:执行绘制指令、填充像素
  3. 合成器阶段:把多个图层合成最终画面

听起来简单,对吧?但实际项目中,这三个阶段经常互相影响。CPU慢了,GPU就得空等;GPU慢了,合成器就得掉帧。

核心观点:瓶颈定位的关键,不是猜,而是测。用数据说话。

CPU负载:别让主线程成为瓶颈

CPU主要负责两件事:事件处理和绘制指令生成。

我个人习惯先看CPU占用率。如果单核跑满,其他核闲着,那大概率是主线程过载。

CPU瓶颈的典型症状

  • 帧率波动大,偶尔卡顿
  • 触摸响应延迟
  • 动画不流畅,像在跳帧

定位方法

我常用的工具是perf和ftrace。在嵌入式Linux上,跑一下:

perf top -p <pid>  # 看哪个函数吃CPU
trace-cmd record -e sched_switch  # 看线程调度情况

如果发现draw()或update()函数耗时异常,那就要深入看了。

小技巧:我曾经在一个项目中,发现CPU占用不高但界面卡。后来用ftrace一查,是线程优先级太低,被其他任务抢占了。调高优先级后,问题解决。

常见CPU瓶颈点

瓶颈类型 典型原因 解决方向
布局计算 频繁触发relayout 减少嵌套、使用缓存
绘制指令生成 大量clip、transform 合并绘制操作
事件处理 触摸事件处理太慢 异步处理、减少锁竞争
内存分配 频繁new/delete 对象池、预分配

GPU负载:填充率才是关键

GPU的活儿很单纯:画像素。但画得快不快,取决于填充率和着色器复杂度。

你想想看,如果屏幕分辨率是1920x1080,一帧要画200万个像素。如果GPU填充率不够,那就只能降帧。

GPU瓶颈的典型症状

  • 帧率稳定但偏低(比如一直30fps)
  • 画面复杂时明显变慢
  • GPU占用率持续高位

定位方法

我建议用GPU profiling工具。比如:

  • ARM Mali:使用Mali Graphics Debugger
  • Qualcomm Adreno:使用Snapdragon Profiler
  • 通用方法:看GPU频率和总线带宽

在Linux上,可以看/sys/class/kgsl/kgsl-3d0/gpu_busy_percentage:

cat /sys/class/kgsl/kgsl-3d0/gpu_busy_percentage
# 输出:85%  # 说明GPU很忙

注意:GPU占用率高不一定就是瓶颈。如果CPU在等GPU,那才是真瓶颈。要结合CPU和GPU的同步点来看。

常见GPU瓶颈点

瓶颈类型 典型原因 解决方向
过度绘制 多层重叠、全屏透明 减少图层、使用裁剪
纹理过大 使用高分辨率图片 压缩纹理、使用mipmap
着色器复杂 大量特效、模糊 简化着色器、预计算
带宽不足 频繁纹理切换 纹理合并、减少切换

合成器负载:被忽视的隐形杀手

合成器这步,很多人会忽略。说白了,就是把多个图层叠在一起。如果图层太多,合成器就得做大量混合操作。

我记得有个项目,界面卡得不行。CPU和GPU都正常,最后发现是图层数太多——32个图层!合成器直接崩溃。

合成器瓶颈的典型症状

  • 界面整体掉帧,但CPU和GPU都不高
  • 滚动时出现撕裂
  • 动画有残影

定位方法

我常用的方法是:

  1. 看图层数:用dumpsys SurfaceFlinger(Android)或weston-info(Wayland)
  2. 看合成方式:是GPU合成还是硬件合成?硬件合成效率高很多
  3. 看帧率:合成器帧率是否匹配显示刷新率
# Android上查看图层信息
dumpsys SurfaceFlinger | grep "Layer"
# 输出类似:Layer 0x1234 (com.example)  # 每个图层一行

避坑指南:我曾经遇到一个坑——透明图层。哪怕只有一个像素是透明的,合成器也得做alpha混合。后来我把透明区域裁剪掉,性能直接翻倍。

常见合成器瓶颈点

瓶颈类型 典型原因 解决方向
图层过多 超过硬件合成上限 合并图层、减少层级
透明混合 大量alpha通道 预乘alpha、减少透明
分辨率不匹配 图层分辨率与屏幕不一致 统一分辨率、使用缩放
同步延迟 vsync等待时间过长 调整帧率、使用双缓冲

瓶颈定位流程图

下面这张图,是我在实际项目中总结的定位流程。你可以照着走一遍:

渲染管线瓶颈定位流程图 发现界面卡顿 CPU占用率是否过高? 是 CPU瓶颈 检查主线程 优化布局/绘制指令 否 GPU占用率是否过高? 是 GPU瓶颈 检查填充率 优化纹理/着色器 否 合成器帧率是否正常? 否 合成器瓶颈 检查图层数 减少透明混合 是 其他问题 检查内存/IO 检查驱动/硬件 定位完成,针对性优化 提示:如果三个环节都正常,检查内存带宽、DMA延迟或驱动bug

实战案例:一个真实的卡顿排查

最后分享一个我实际遇到的案例。

项目是智能家居控制面板,界面卡顿严重。我按上面的流程走了一遍:

  1. 看CPU:占用率30%,不高
  2. 看GPU:占用率20%,也不高
  3. 看合成器:图层数28个!远超硬件合成上限(16个)

问题找到了。解决方案是合并图层,把背景、文字、图标合并成一张纹理。图层数降到12个,帧率从25fps升到55fps。

教训:别想当然。数据不会骗人。每次卡顿,都先跑一遍定位流程,别急着改代码。

嗯,这一章就到这里。记住:定位瓶颈,比优化更重要。方向错了,越优化越糟。