Edit online

显示对接

Read time: 4 minute(s)

主要包括三部分:

  1. 绘制 buffer 初始化。函数如下:
    void lv_disp_draw_buf_init(lv_disp_draw_buf_t * draw_buf, void * buf1, void * buf2, uint32_t size_in_px_cnt)
    • buf1:当为单缓冲或多缓冲的时候,都要设置此 buffer
    • buf2:当选择双缓冲的时候,需要配置此 buffer,单缓冲不需要
    • size_in_px_cnt: 以像素为单位的 buf 大小

      double_frame

      1. 双缓冲
  2. flush_cb 对接

    下方以双缓冲为例说明 flush_cb 回调函数的处理流程。绘制模式有 refresh 和 direct_mode 两种:

    • 全刷新模式,每一帧都刷新整个显示屏


      full_flush_cb

      2. 全刷新模式

      在虚线框中为 cb 中处理部分,在全刷新的流程中,直接通过 pan_display 接口送当前绘制 buffer 到显示,然后等待 vsync 中断, 等到中断后,当前的绘制 buffer 就真正的在显示屏中显示出来,然后调用 ready 通知 LVGL 框架已经 flush 结束, 最后在 LVGL 框架中会进行绘制 buffer 的交换。

    • 局部刷新,每一帧只刷新需要更新的无效区域(可以有多个无效区域)


      invalid_area

      3. 无效区域

      direct_flush_cb

      4. 局部刷新模式

      上图中的示例,为了方便描述每一帧都有两个无效区域(invalid area0 和 area1),LVGL 可以支持更多的无效区域,到了最后一个无效区域, 说明当前帧的数据已经处理完,才把绘制 buffer 送显示,然后进行 buffer 交换

      flush_cb 的实现代码 flush 如下:
      static void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t *color_p)
      {
          lv_disp_t * disp = _lv_refr_get_disp_refreshing();
          lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp);
      
          if (!disp->driver->direct_mode || draw_buf->flushing_last) {
              struct fb_var_screeninfo var = {0};
      
              if (ioctl(g_fb, FBIOGET_VSCREENINFO, &var) < 0) {
                  LV_LOG_WARN("ioctl FBIOGET_VSCREENINFO");
                  return;
              }
      
              if (color_p == (lv_color_t *)g_frame_buf[0]) {
                  var.xoffset = 0;
                  var.yoffset = 0;
              } else {
                  var.xoffset = 0;
                  var.yoffset = disp_drv.ver_res;
              }
      
              if (ioctl(g_fb, FBIOPAN_DISPLAY, &var) == 0) {
                  int zero = 0;
                  if (ioctl(g_fb, AICFB_WAIT_FOR_VSYNC, &zero) < 0) {
                      LV_LOG_WARN("ioctl AICFB_WAIT_FOR_VSYNC fail");
                      return;
                  }
              } else {
                  LV_LOG_WARN("pan display err");
              }
      
              if (drv->direct_mode == 1) {
                  for (int i = 0; i < disp->inv_p; i++) {
                      if (disp->inv_area_joined[i] == 0) {
                          sync_disp_buf(drv, color_p, &disp->inv_areas[i]);
                      }
                  }
              }
      
              lv_disp_flush_ready(drv);
          } else {
              lv_disp_flush_ready(drv);
          }
      }
  3. 2D 硬件加速对接,主要对接 lv_draw_ctx_t 中的绘制函数
    成员 说明 是否硬件加速
    void *buf 当前要绘制的 buffer -
    const lv_area_t * clip_area 绘制区域裁剪(以屏幕为参考的绝对坐标) -
    void (*draw_rect)() 绘制矩形(包括圆角、阴影、渐变等)
    void (*draw_arc)() 绘制弧形
    void (*draw_img_decoded)() 绘制已经解码后的图像
    lv_res_t (*draw_img)() 绘制图像(包括图片解码)
    void (*draw_letter)() 绘制文字
    void (*draw_line)() 绘制直线
    void (*draw_polygon)() 绘制多边形
    在 t(重定义了 t)结构体中包含 t 和 blend 函数:
    typedef struct {
        lv_draw_ctx_t base_draw;
    
        /** Fill an area of the destination buffer with a color*/
        void (*blend)(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc);
    } lv_draw_sw_ctx_t;
    在 rect、draw_line 等操作的功能由多个步骤组成,虽然我们没有对这些接口进行硬件加速,但是这些操作的部分实现 会调用到 blend,我们对 blend 接口进行了硬件加速对接:
    void lv_draw_aic_ctx_init(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx)
    {
        lv_draw_sw_init_ctx(drv, draw_ctx);
        lv_draw_aic_ctx_t * aic_draw_ctx = (lv_draw_aic_ctx_t *)draw_ctx;
    
        aic_draw_ctx->blend = lv_draw_aic_blend;
        aic_draw_ctx->base_draw.draw_img = lv_draw_aic_draw_img;
        aic_draw_ctx->base_draw.draw_img_decoded = lv_draw_aic_img_decoded;
    
        return;
    }

    先调用 ctx 函数把所有绘制操作都初始化为软件实现,然后对可以硬件加速的接口重新实现, 覆盖原来的软件实现。

  4. 所有的显示相关功能都包含在 t 结构体中:

    1. 通过 init 来初始化 lv_disp_drv_t 结构体

    2. 通过 init 初始化绘制 buffer

    3. 通过回调 cb 来注册显示接口

    4. 通过 init 来注册 2D 硬件加速相关接口

    5. 通过 register 来注册 lv_disp_drv_t

    在源文件 c 中的函数 lv_port_disp_init 配置刷新模式,局部刷新模式配置如下:
    disp_drv.full_refresh = 0;
    disp_drv.direct_mode = 1;
    全刷新模式参数配置如下:
    disp_drv.full_refresh = 1;
    disp_drv.direct_mode = 0;