App:Library:LVGL:docs:Porting:Add custom GPU
https://docs.lvgl.io/8.2/porting/gpu.html
Add custom GPU
英文 自動翻訳 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.
Draw context
英文 自動翻訳 The core structure of drawing is
lv_draw_ctx_t
. It contains a pointer to a buffer where drawing should happen and a couple of callbacks to draw rectangles, texts, and other primitives.
Fields
英文 自動翻訳 lv_draw_ctx_t
has the following fields:void * buf
Pointer to a buffer to draw intolv_area_t * buf_area
The position and size ofbuf
(absolute coordinates)const lv_area_t * clip_area
The current clip area with absolute coordinates, always the same or smaller thanbuf_area
. All drawings should be clipped to this area.void (*draw_rect)()
Draw a rectangle with shadow, gradient, border, etc.void (*draw_arc)()
Draw an arcvoid (*draw_img_decoded)()
Draw an (A)RGB image that is already decoded by LVGL.lv_res_t (*draw_img)()
Draw an image before decoding it (it bypasses LVGL's internal image decoders)void (*draw_letter)()
Draw a lettervoid (*draw_line)()
Draw a linevoid (*draw_polygon)()
Draw a polygonvoid (*draw_bg)()
Replace the buffer with a rect without decoration like radius or borders.void (*wait_for_finish)()
Wait until all background operation are finished. (E.g. GPU operations)void * user_data
Custom user data for arbitrary purpose
(For the sake of simplicity the parameters of the callbacks are not shown here.)
All
draw_*
callbacks receive a pointer to the currentdraw_ctx
as their first parameter. Among the other parameters there is a descriptor that tells what to draw, e.g. fordraw_rect
it's called lv_draw_rect_dsc_t, forlv_draw_line
it's called lv_draw_line_dsc_t, etc.To correctly render according to a
draw_dsc
you need to be familiar with the 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.
Initialization
英文 自動翻訳 The
lv_disp_drv_t
has 4 fields related to the draw context:lv_draw_ctx_t * draw_ctx
Pointer to thedraw_ctx
of this displayvoid (*draw_ctx_init)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)
Callback to initialize adraw_ctx
void (*draw_ctx_deinit)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)
Callback to de-initialize adraw_ctx
size_t draw_ctx_size
Size of the draw context structure. E.g.sizeof(lv_draw_sw_ctx_t)
When you ignore these fields, LVGL will set default values for callbacks and size in
lv_disp_drv_init()
based on the configuration inlv_conf.h
.lv_disp_drv_register()
will allocate adraw_ctx
based ondraw_ctx_size
and calldraw_ctx_init()
on it.However, you can overwrite the callbacks and the size values before calling
lv_disp_drv_register()
. It makes it possible to use your owndraw_ctx
with your own callbacks.
Software renderer
英文 自動翻訳 LVGL's built in software renderer extends the basic
lv_draw_ctx_t
structure and sets the draw callbacks. It looks like this:typedef struct { /** Include the basic draw_ctx type*/ lv_draw_ctx_t base_draw; /** Blend a color or image to an area*/ void (*blend)(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc); } lv_draw_sw_ctx_t;
Set the draw callbacks in
draw_ctx_init()
like:draw_sw_ctx->base_draw.draw_rect = lv_draw_sw_rect; draw_sw_ctx->base_draw.draw_letter = lv_draw_sw_letter; ...
Blend callback
英文 自動翻訳 As you saw above the software renderer adds the
blend
callback field. It's a special callback related to how the software renderer works. All draw operations end up in theblend
callback which can either fill an area or copy an image to an area by considering an optional mask.The
lv_draw_sw_blend_dsc_t
parameter describes what and how to blend. It has the following fields:const lv_area_t * blend_area
The area with absolute coordinates to draw ondraw_ctx->buf
. Ifsrc_buf
is set, it's the coordinates of the image to blend.const lv_color_t * src_buf
Pointer to an image to blend. If set,color
is ignored. If not set fillblend_area
withcolor
lv_color_t color
Fill color. Used only ifsrc_buf == NULL
lv_opa_t * mask_buf
NULL if ignored, or an alpha mask to apply onblend_area
lv_draw_mask_res_t mask_res
The result of the previous mask operation. (LV_DRAW_MASK_RES_...
)const lv_area_t * mask_area
The area ofmask_buf
with absolute coordinateslv_opa_t opa
The overall opacitylv_blend_mode_t blend_mode
E.g.LV_BLEND_MODE_ADDITIVE
Extend the software renderer
New blend callback
英文 自動翻訳 Let's take a practical example: you would like to use your MCUs GPU for color fill operations only.
As all draw callbacks call
blend
callback to fill an area in the end only theblend
callback needs to be overwritten.First extend
lv_draw_sw_ctx_t
:/*We don't add new fields, so just for clarity add new type*/ typedef lv_draw_sw_ctx_t my_draw_ctx_t; void my_draw_ctx_init(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx) { /*Initialize the parent type first */ lv_draw_sw_init_ctx(drv, draw_ctx); /*Change some callbacks*/ my_draw_ctx_t * my_draw_ctx = (my_draw_ctx_t *)draw_ctx; my_draw_ctx->blend = my_draw_blend; my_draw_ctx->base_draw.wait_for_finish = my_gpu_wait; }
After calling
lv_disp_draw_init(&drv)
you can assign the newdraw_ctx_init
callback and setdraw_ctx_size
to overwrite the defaults:static lv_disp_drv_t drv; lv_disp_draw_init(&drv); drv->hor_res = my_hor_res; drv->ver_res = my_ver_res; drv->flush_cb = my_flush_cb; /*New draw ctx settings*/ drv->draw_ctx_init = my_draw_ctx_init; drv->draw_ctx_size = sizeof(my_draw_ctx_t); lv_disp_drv_register(&drv);
This way when LVGL calls
blend
it will callmy_draw_blend
and we can do custom GPU operations. Here is a complete example:void my_draw_blend(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc) { /*Let's get the blend area which is the intersection of the area to fill and the clip area.*/ lv_area_t blend_area; if(!_lv_area_intersect(&blend_area, dsc->blend_area, draw_ctx->clip_area)) return; /*Fully clipped, nothing to do*/ /*Fill only non masked, fully opaque, normal blended and not too small areas*/ if(dsc->src_buf == NULL && dsc->mask == NULL && dsc->opa >= LV_OPA_MAX && dsc->blend_mode == LV_BLEND_MODE_NORMAL && lv_area_get_size(&blend_area) > 100) { /*Got the first pixel on the buffer*/ lv_coord_t dest_stride = lv_area_get_width(draw_ctx->buf_area); /*Width of the destination buffer*/ lv_color_t * dest_buf = draw_ctx->buf; dest_buf += dest_stride * (blend_area.y1 - draw_ctx->buf_area->y1) + (blend_area.x1 - draw_ctx->buf_area->x1); /*Make the blend area relative to the buffer*/ lv_area_move(&blend_area, -draw_ctx->buf_area->x1, -draw_ctx->buf_area->y1); /*Call your custom gou fill function to fill blend_area, on dest_buf with dsc->color*/ my_gpu_fill(dest_buf, dest_stride, &blend_area, dsc->color); } /*Fallback: the GPU doesn't support these settings. Call the SW renderer.*/ else { lv_draw_sw_blend_basic(draw_ctx, dsc); } }
The implementation of wait callback is much simpler:
void my_gpu_wait(lv_draw_ctx_t * draw_ctx) { while(my_gpu_is_working()); /*Call SW renderer's wait callback too*/ lv_draw_sw_wait_for_finish(draw_ctx); }
New rectangle drawer
英文 自動翻訳 If your MCU has a more powerful GPU that can draw e.g. rounded rectangles you can replace the original software drawer too. A custom
draw_rect
callback might look like this:void my_draw_rect(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords) { if(lv_draw_mask_is_any(coords) == false && dsc->grad == NULL && dsc->bg_img_src == NULL && dsc->shadow_width == 0 && dsc->blend_mode = LV_BLEND_MODE_NORMAL) { /*Draw the background*/ my_bg_drawer(draw_ctx, coords, dsc->bg_color, dsc->radius); /*Draw the border if any*/ if(dsc->border_width) { my_border_drawer(draw_ctx, coords, dsc->border_width, dsc->border_color, dsc->border_opa) } /*Draw the outline if any*/ if(dsc->outline_width) { my_outline_drawer(draw_ctx, coords, dsc->outline_width, dsc->outline_color, dsc->outline_opa, dsc->outline_pad) } } /*Fallback*/ else { lv_draw_sw_rect(draw_ctx, dsc, coords); } }
my_draw_rect
can fully bypass the use ofblend
callback if needed.
Fully custom draw engine
英文 自動翻訳 For example if your MCU/MPU supports a powerful vector graphics engine you might use only that instead of LVGL's SW renderer. In this case, you need to base the renderer on the basic
lv_draw_ctx_t
(instead oflv_draw_sw_ctx_t
) and extend/initialize it as you wish.