小区域渲染场景下的帧率统计异常排查

现象描述

作者在 SOC_LVGL 这个仓库分支开发帧率测试示例。
但我发现,当测试的区域比较小的时候,LVGL 自带的性能监视器会出现 FPS 满帧运行的现象。
以下是 lv_conf.h 的一行代码:

#define LV_DISP_DEF_REFR_PERIOD 1

改代码定义了 LVGL 的刷新间隔 LV_DISP_DEF_REFR_PERIOD,单位是ms。
这里设置为 1,意味着 FPS (也就是帧率)的上限是 1000
如果我设置成 16,那么就是最多就 62 帧,这种情况下跑满,我是不会怀疑 LVGL 的。
可是好巧不巧,我就是设置了 1,试图榨干它的性能,结果跑满了?离谱到我觉得不可能。
于是,作者开始排查问题,最终成功解决,破除了这个满帧运行的假象!

问题分析

问题最终追溯到 LVGL 自带的性能监视器 lv_refr.c
以下是 lv_refr.c 中问题所处的部分代码片段:

void _lv_disp_refr_timer(lv_timer_t * tmr) {
...
// 如果启用了性能监视器 LV_USE_PERF_MONITOR 和 LV_USE_LABEL
#if LV_USE_PERF_MONITOR && LV_USE_LABEL
    // 用来显示数据的标签,就是在屏幕上看到的 xx FPS 和 xx% CPU
    lv_obj_t * perf_label = perf_monitor.perf_label;
    if(perf_label == NULL) {
        perf_label = lv_label_create(lv_layer_sys());
        ...
        perf_monitor.perf_label = perf_label;
    }

    // if 收集 300ms 的渲染数据,然后交给 else 进行帧率计算,最后回到 if
    if(lv_tick_elaps(perf_monitor.perf_last_time) < 300) {
        // 如果 300ms 内累计的渲染总像素数量 px_num 大于该数值,则统计一帧
        // 否则就会忽略该帧
        if(px_num > 5000) {
            perf_monitor.elaps_sum += elaps;
            perf_monitor.frame_cnt ++; // 程序初始化后 frame_cnt 为 0
        }
    }
    else {
        perf_monitor.perf_last_time = lv_tick_get();

        // 计算帧率上限,这里的 period 就等于 LV_DISP_DEF_REFR_PERIOD
        // 也就是我设置的 1,代入计算可得 fps_limit = 1000
        uint32_t fps_limit = 1000 / disp_refr->refr_timer->period;

        uint32_t fps;

        if(perf_monitor.elaps_sum == 0) {
            perf_monitor.elaps_sum = 1;
        }

        // 这里是问题的直接体现
        // 如果 frame_cnt 一直为 0,那么帧率将一直等于上限也就是满帧运行
        // 那为什么 frame_cnt 会一直为 0
        // 因为当前设计的测试程序在 300ms 累计渲染的像素总数小于 5000
        // 从而导致 LVGL 没有对这些渲染进行帧计数,所以 frame_cnt 会一直为 0
        if(perf_monitor.frame_cnt == 0) {
            fps = fps_limit;
        }
        else {
            fps = (1000 * perf_monitor.frame_cnt) / perf_monitor.elaps_sum;
        }
        ...
        // 这里是标签的数据更新
        lv_label_set_text_fmt(perf_label, "%"LV_PRIu32" FPS\n%"LV_PRIu32"%% CPU", fps, cpu);
    }
#endif
...
}

如何解决

只需要修改源码,把原先的 5000 这个计数阈值改小即可。
为了让最小渲染区域得到计数,我改成了 1000
因为项目最小的测试区域是 32 * 32 = 1024 刚好大于 1000