帧率控制:别让屏幕跑得比系统快
帧率控制,说白了就是让显示输出和内容生成保持同步。你想想看,如果GPU拼命画图,但屏幕刷新跟不上,或者反过来屏幕刷新太快但内容没准备好,都会出现撕裂或卡顿。
在MTK8678上,我习惯用动态帧率调整策略。核心思路是:根据当前场景,动态决定显示帧率。
核心原则:静态画面用低帧率(30fps甚至更低),动态画面用高帧率(60fps或120fps)。
具体怎么实现?看代码:
// 帧率控制核心逻辑 static int mtk_drm_adjust_fps(struct drm_crtc *crtc, int target_fps) { struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); int current_fps = mtk_crtc->fps; // 检查硬件能力 if (target_fps > mtk_crtc->max_fps) { DRM_WARN("Target FPS %d exceeds max %d, clamping\n", target_fps, mtk_crtc->max_fps); target_fps = mtk_crtc->max_fps; } // 设置VBLANK间隔 mtk_crtc->vblank_interval = DIV_ROUND_CLOSEST(1000000, target_fps); // 更新硬件寄存器 mtk_dsi_set_fps(mtk_crtc->dsi, target_fps); return 0; }这里有个坑,我曾经遇到过:帧率切换太快会导致屏幕闪烁。所以我在项目中加了个平滑过渡机制,每次帧率变化不超过10fps,效果好了很多。
我的经验:在车载多屏场景下,副屏(比如后排娱乐屏)用30fps就够了,主屏保持60fps。这样能省不少带宽和功耗。
GPU合成 vs 硬件合成:选对工具干对活
这个问题,说白了就是「谁来做图层叠加」的选择题。GPU合成灵活但费电,硬件合成省电但有限制。
MTK8678的硬件合成器(HWC)支持最多4个图层的硬件叠加。超过这个数,就得走GPU合成。
| 合成方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| GPU合成 | 灵活,支持任意图层数 | 功耗高,带宽占用大 | 复杂UI、游戏 |
| 硬件合成 | 功耗低,延迟小 | 图层数有限,格式限制 | 视频播放、简单UI |
我建议的切换策略是这样的:
- 静态场景(桌面、设置界面):强制走硬件合成
- 动态场景(视频、动画):根据图层数动态切换
- 游戏场景:直接走GPU合成,保证帧率
代码实现上,我习惯在SurfaceFlinger层面做判断:
// 合成方式选择逻辑 static int choose_composition_mode(struct display_device *ddev, int layer_count) { // 硬件合成能力检查 if (layer_count <= ddev->hwc_max_layers) { // 检查图层格式是否支持 if (check_hwc_compatible(ddev)) { return COMPOSITION_HWC; // 硬件合成 } } // 回退到GPU合成 return COMPOSITION_GPU; }注意:硬件合成不支持旋转和缩放操作。如果你的图层做了旋转,哪怕只有1个图层,也得走GPU合成。我曾经因为这个bug排查了三天...
带宽优化:别让数据堵在路上
多屏显示最大的瓶颈就是带宽。MTK8678的显示子系统带宽是有限的,三个屏幕同时跑4K@60fps,带宽直接爆掉。
我常用的带宽优化手段:
- 压缩传输:使用DSC(显示流压缩)技术,压缩比可达3:1
- 分时传输:不同屏幕的刷新错开,避免同时请求带宽
- 缓存复用:相同内容只传输一次,多个屏幕共享
来看一个带宽计算的例子:
// 带宽计算示例 static unsigned long calc_display_bandwidth(struct display_config *cfg) { unsigned long bw = 0; // 每个屏幕的带宽 = 分辨率 × 色深 × 帧率 for (int i = 0; i < cfg->screen_count; i++) { struct screen_info *screen = &cfg->screens[i]; unsigned long screen_bw; screen_bw = screen->width * screen->height * screen->bpp * screen->fps; // 如果启用DSC压缩 if (screen->dsc_enabled) { screen_bw /= 3; // 3:1压缩比 } bw += screen_bw; } return bw; }我的建议:在项目初期就做好带宽预算。我曾经有个项目,三个屏幕加起来带宽需求超过总线带宽的80%,结果一跑就卡。后来通过分时刷新和DSC压缩,降到了60%以下,问题解决。
低功耗显示策略:省电就是省钱
低功耗显示,说白了就是「不该亮的时候别亮,该亮的时候少亮」。MTK8678在这方面做了不少硬件支持。
我常用的低功耗策略:
- 局部刷新:只更新变化区域,静态区域不刷新
- 背光调节:根据环境光自动调节背光亮度
- 帧率降级:无操作时自动降帧
- 屏幕关闭:副屏长时间无操作自动关闭
来看一个局部刷新的实现:
// 局部刷新区域计算 static struct drm_clip_rect calc_damage_region(struct drm_plane *plane) { struct drm_clip_rect damage = {0}; // 获取当前帧和上一帧的差异 damage.x1 = min(plane->state->src_x, plane->old_state->src_x); damage.y1 = min(plane->state->src_y, plane->old_state->src_y); damage.x2 = max(plane->state->src_x + plane->state->src_w, plane->old_state->src_x + plane->old_state->src_w); damage.y2 = max(plane->state->src_y + plane->state->src_h, plane->old_state->src_y + plane->old_state->src_h); return damage; }关键数据:在我测试的项目中,启用局部刷新后,静态场景功耗降低了40%。如果配合帧率降级,整体功耗可以降低50%以上。
嗯,最后说一句。性能优化不是一蹴而就的事,需要反复调优和验证。我建议你在开发板上先跑通基础功能,然后用perfetto或systrace抓取性能数据,找到瓶颈再针对性优化。别一上来就想着把所有优化都加上,那样反而容易出问题。
好了,今天就聊到这里。下一章我们讲多屏同步与帧缓冲管理,到时候见。