acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

gpu.md (9397B)

      1 ```eval_rst
      2 .. include:: /header.rst
      3 :github_url: |github_link_base|/porting/gpu.md
      4 ```
      5 # Add custom GPU
      6 LVGL has a flexible and extendable draw pipeline. You can hook it to do some rendering with a GPU or even completely replace the built-in software renderer.
      7 
      8 ## Draw context
      9 The core structure of drawing is `lv_draw_ctx_t`.
     10 It contains a pointer to a buffer where drawing should happen and a couple of callbacks to draw rectangles, texts, and other primitives.
     11 
     12 ### Fields
     13 `lv_draw_ctx_t` has the following fields:
     14 - `void * buf` Pointer to a buffer to draw into
     15 - `lv_area_t * buf_area` The position and size of `buf` (absolute coordinates)
     16 - `const lv_area_t * clip_area` The current clip area with absolute coordinates, always the same or smaller than `buf_area`. All drawings should be clipped to this area.
     17 - `void (*draw_rect)()` Draw a rectangle with shadow, gradient, border, etc.
     18 - `void (*draw_arc)()` Draw an arc
     19 - `void (*draw_img_decoded)()` Draw an (A)RGB image that is already decoded by LVGL.
     20 - `lv_res_t (*draw_img)()` Draw an image before decoding it (it bypasses LVGL's internal image decoders)
     21 - `void (*draw_letter)()` Draw a letter
     22 - `void (*draw_line)()` Draw a line
     23 - `void (*draw_polygon)()` Draw a polygon
     24 - `void (*draw_bg)()` Replace the buffer with a rect without decoration like radius or borders.
     25 - `void (*wait_for_finish)()` Wait until all background operation are finished. (E.g. GPU operations)
     26 - `void * user_data` Custom user data for arbitrary purpose
     27 
     28 (For the sake of simplicity the parameters of the callbacks are not shown here.)
     29 
     30 All `draw_*` callbacks receive a pointer to the current `draw_ctx` as their first parameter. Among the other parameters there is a descriptor that tells what to draw,
     31 e.g. for `draw_rect` it's called [lv_draw_rect_dsc_t](https://github.com/lvgl/lvgl/blob/master/src/draw/lv_draw_rect.h),
     32 for `lv_draw_line` it's called [lv_draw_line_dsc_t](https://github.com/lvgl/lvgl/blob/master/src/draw/lv_draw_line.h), etc.
     33 
     34 To correctly render according to a `draw_dsc` you need to be familiar with the [Boxing model](https://docs.lvgl.io/master/overview/coords.html#boxing-model) of LVGL and the meanings of the fields. The name and meaning of the fields are identical to name and meaning of the [Style properties](https://docs.lvgl.io/master/overview/style-props.html).
     35 
     36 ### Initialization
     37 The `lv_disp_drv_t` has 4 fields related to the draw context:
     38 - `lv_draw_ctx_t * draw_ctx` Pointer to the `draw_ctx` of this display
     39 - `void (*draw_ctx_init)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)` Callback to initialize a `draw_ctx`
     40 - `void (*draw_ctx_deinit)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)` Callback to de-initialize a `draw_ctx`
     41 - `size_t draw_ctx_size` Size of the draw context structure. E.g. `sizeof(lv_draw_sw_ctx_t)`
     42 
     43 When you ignore these fields, LVGL will set default values for callbacks and size in `lv_disp_drv_init()` based on the configuration in `lv_conf.h`.
     44 `lv_disp_drv_register()` will allocate a `draw_ctx` based on `draw_ctx_size` and call `draw_ctx_init()` on it.
     45 
     46 However, you can overwrite the callbacks and the size values before calling `lv_disp_drv_register()`.
     47 It makes it possible to use your own `draw_ctx` with your own callbacks.
     48 
     49 
     50 ## Software renderer
     51 LVGL's built in software renderer extends the basic `lv_draw_ctx_t` structure and sets the draw callbacks. It looks like this:
     52 ```c
     53 typedef struct {
     54    /** Include the basic draw_ctx type*/
     55     lv_draw_ctx_t base_draw;
     56 
     57     /** Blend a color or image to an area*/
     58     void (*blend)(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc);
     59 } lv_draw_sw_ctx_t;
     60 ```
     61 
     62 Set the draw callbacks in `draw_ctx_init()` like:
     63 ```c
     64 draw_sw_ctx->base_draw.draw_rect = lv_draw_sw_rect;
     65 draw_sw_ctx->base_draw.draw_letter = lv_draw_sw_letter;
     66 ...
     67 ```
     68 
     69 ### Blend callback
     70 As you saw above the software renderer adds the `blend` callback field. It's a special callback related to how the software renderer works.
     71 All draw operations end up in the `blend` callback which can either fill an area or copy an image to an area by considering an optional mask.
     72 
     73 The `lv_draw_sw_blend_dsc_t` parameter describes what and how to blend. It has the following fields:
     74 - `const lv_area_t * blend_area` The area with absolute coordinates to draw on `draw_ctx->buf`. If `src_buf` is set, it's the coordinates of the image to blend.
     75 - `const lv_color_t * src_buf` Pointer to an image to blend. If set, `color` is ignored. If not set fill `blend_area` with `color`
     76 - `lv_color_t color` Fill color. Used only if `src_buf == NULL`
     77 - `lv_opa_t * mask_buf` NULL if ignored, or an alpha mask to apply on `blend_area`
     78 - `lv_draw_mask_res_t mask_res` The result of the previous mask operation. (`LV_DRAW_MASK_RES_...`)
     79 - `const lv_area_t * mask_area` The area of `mask_buf` with absolute coordinates
     80 - `lv_opa_t opa` The overall opacity
     81 - `lv_blend_mode_t blend_mode` E.g. `LV_BLEND_MODE_ADDITIVE`
     82 
     83 
     84 ## Extend the software renderer
     85 
     86 ### New blend callback
     87 
     88 Let's take a practical example: you would like to use your MCUs GPU for color fill operations only.
     89 
     90 As all draw callbacks call `blend` callback to fill an area in the end only the `blend` callback needs to be overwritten.
     91 
     92 First extend `lv_draw_sw_ctx_t`:
     93 ```c
     94 
     95 /*We don't add new fields, so just for clarity add new type*/
     96 typedef lv_draw_sw_ctx_t my_draw_ctx_t;
     97 
     98 void my_draw_ctx_init(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx)
     99 {
    100     /*Initialize the parent type first */
    101     lv_draw_sw_init_ctx(drv, draw_ctx);
    102 
    103     /*Change some callbacks*/
    104     my_draw_ctx_t * my_draw_ctx = (my_draw_ctx_t *)draw_ctx;
    105 
    106     my_draw_ctx->blend = my_draw_blend;
    107     my_draw_ctx->base_draw.wait_for_finish = my_gpu_wait;
    108 }
    109 ```
    110 
    111 After calling `lv_disp_draw_init(&drv)` you can assign the new `draw_ctx_init` callback and set `draw_ctx_size` to overwrite the defaults:
    112 ```c
    113 static lv_disp_drv_t drv;
    114 lv_disp_draw_init(&drv);
    115 drv->hor_res = my_hor_res;
    116 drv->ver_res = my_ver_res;
    117 drv->flush_cb = my_flush_cb;
    118 
    119 /*New draw ctx settings*/
    120 drv->draw_ctx_init = my_draw_ctx_init;
    121 drv->draw_ctx_size = sizeof(my_draw_ctx_t);
    122 
    123 lv_disp_drv_register(&drv);
    124 ```
    125 
    126 This way when LVGL calls `blend` it will call `my_draw_blend` and we can do custom GPU operations. Here is a complete example:
    127 ```c
    128 void my_draw_blend(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc)
    129 {
    130     /*Let's get the blend area which is the intersection of the area to fill and the clip area.*/
    131     lv_area_t blend_area;
    132     if(!_lv_area_intersect(&blend_area, dsc->blend_area, draw_ctx->clip_area)) return;  /*Fully clipped, nothing to do*/
    133 
    134     /*Fill only non masked, fully opaque, normal blended and not too small areas*/
    135     if(dsc->src_buf == NULL && dsc->mask == NULL && dsc->opa >= LV_OPA_MAX &&
    136        dsc->blend_mode == LV_BLEND_MODE_NORMAL && lv_area_get_size(&blend_area) > 100) {
    137 
    138         /*Got the first pixel on the buffer*/
    139         lv_coord_t dest_stride = lv_area_get_width(draw_ctx->buf_area); /*Width of the destination buffer*/
    140         lv_color_t * dest_buf = draw_ctx->buf;
    141         dest_buf += dest_stride * (blend_area.y1 - draw_ctx->buf_area->y1) + (blend_area.x1 - draw_ctx->buf_area->x1);
    142 
    143         /*Make the blend area relative to the buffer*/      
    144         lv_area_move(&blend_area, -draw_ctx->buf_area->x1, -draw_ctx->buf_area->y1);
    145        
    146         /*Call your custom gou fill function to fill blend_area, on dest_buf with dsc->color*/  
    147         my_gpu_fill(dest_buf, dest_stride, &blend_area, dsc->color);
    148     }
    149     /*Fallback: the GPU doesn't support these settings. Call the SW renderer.*/
    150     else {
    151       lv_draw_sw_blend_basic(draw_ctx, dsc);
    152     }
    153 }
    154 ```
    155 
    156 The implementation of wait callback is much simpler:
    157 ```c
    158 void my_gpu_wait(lv_draw_ctx_t * draw_ctx)
    159 {
    160     while(my_gpu_is_working());
    161    
    162     /*Call SW renderer's wait callback too*/
    163     lv_draw_sw_wait_for_finish(draw_ctx);
    164 }
    165 ```
    166 
    167 ### New rectangle drawer
    168 If your MCU has a more powerful GPU that can draw e.g. rounded rectangles you can replace the original software drawer too.
    169 A custom `draw_rect` callback might look like this:
    170 ```c
    171 void my_draw_rect(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
    172 {
    173   if(lv_draw_mask_is_any(coords) == false && dsc->grad == NULL && dsc->bg_img_src == NULL &&
    174      dsc->shadow_width == 0 && dsc->blend_mode = LV_BLEND_MODE_NORMAL)
    175   {
    176     /*Draw the background*/
    177     my_bg_drawer(draw_ctx, coords, dsc->bg_color, dsc->radius);
    178    
    179     /*Draw the border if any*/
    180     if(dsc->border_width) {
    181       my_border_drawer(draw_ctx, coords, dsc->border_width, dsc->border_color, dsc->border_opa)
    182     }
    183    
    184     /*Draw the outline if any*/
    185     if(dsc->outline_width) {
    186       my_outline_drawer(draw_ctx, coords, dsc->outline_width, dsc->outline_color, dsc->outline_opa, dsc->outline_pad)
    187     }
    188   }
    189   /*Fallback*/
    190   else {
    191     lv_draw_sw_rect(draw_ctx, dsc, coords);
    192   }
    193 }
    194 ```
    195 
    196 `my_draw_rect` can fully bypass the use of `blend` callback if needed.
    197 
    198 ## Fully custom draw engine
    199 
    200 For example if your MCU/MPU supports a powerful vector graphics engine you might use only that instead of LVGL's SW renderer.
    201 In this case, you need to base the renderer on the basic `lv_draw_ctx_t` (instead of `lv_draw_sw_ctx_t`) and extend/initialize it as you wish.
    202