feat(NemaGFX): add vector draw task support (#8938)

This commit is contained in:
Liam Howatt
2025-10-02 16:26:54 +02:00
committed by GitHub
parent b97b928b21
commit 75efb0a12e
11 changed files with 303 additions and 19 deletions
@@ -75,6 +75,36 @@ provided implementations by setting :c:macro:`LV_USE_NEMA_HAL` to a value other
:c:macro:`LV_NEMA_HAL_CUSTOM`.
Vector Graphics
***************
The NeoChrom VG driver in LVGL can render vector graphics Widgets using NeoChrom VG's
hardware support for vector graphics drawing. You can display SVG files in your application.
See the SVG examples for usage.
To use vector graphics with NeoChrom, you should enable the following configs in ``lv_conf.h``.
.. code-block::
LV_USE_NEMA_GFX 1
LV_USE_NEMA_VG 1
LV_USE_VECTOR_GRAPHIC 1
LV_USE_MATRIX 1
LV_USE_FLOAT 1
To use the SVG widget, additionally enable ``LV_USE_SVG``.
If there is RAM available, SVG performance can be increased by enabling the image cache,
``LV_CACHE_DEF_SIZE``.
``LV_CACHE_DEF_SIZE`` is a cache size in bytes. If it is large enough for your SVGs,
it will cache decoded SVG data so it does not need to be parsed every refresh, significantly
reducing SVG redraw time.
``LV_USE_DEMO_VECTOR_GRAPHIC`` is a demo you can enable which draws some vector graphics shapes.
Gradient and image fills are not supported yet, as well as dashed strokes. These are
missing from the demo when it is run with the NeoChrom driver.
TSC Images
**********
+2
View File
@@ -0,0 +1,2 @@
<svg width="12cm" height="4cm" viewBox="0 0 1200 400">
<circle cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10"/></svg>

After

Width:  |  Height:  |  Size: 139 B

+12 -1
View File
@@ -1,6 +1,17 @@
Load and render SVG data
---------------------------------------
------------------------
.. lv_example:: libs/svg/lv_example_svg_1
:language: c
Load and render SVG data from a file
------------------------------------
.. lv_example:: libs/svg/lv_example_svg_2
:language: c
Load and render SVG data in a draw event
----------------------------------------
.. lv_example:: libs/svg/lv_example_svg_3
:language: c
+2
View File
@@ -26,6 +26,8 @@ extern "C" {
* GLOBAL PROTOTYPES
**********************/
void lv_example_svg_1(void);
void lv_example_svg_2(void);
void lv_example_svg_3(void);
/**********************
* MACROS
+13 -13
View File
@@ -3,22 +3,22 @@
#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC
/**
* Load an SVG data
* Load an SVG from data
*/
static void event_cb(lv_event_t * e)
{
static char svg_data[] = "<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\">"
"<circle cx=\"600\" cy=\"200\" r=\"100\" fill=\"red\" stroke=\"blue\" stroke-width=\"10\"/></svg>";
lv_layer_t * layer = lv_event_get_layer(e);
lv_svg_node_t * svg = lv_svg_load_data(svg_data, sizeof(svg_data) / sizeof(char));
lv_draw_svg(layer, svg);
lv_svg_node_delete(svg);
}
void lv_example_svg_1(void)
{
lv_obj_add_event_cb(lv_screen_active(), event_cb, LV_EVENT_DRAW_MAIN, NULL);
static const char svg_data[] = "<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\">"
"<circle cx=\"600\" cy=\"200\" r=\"100\" fill=\"red\" stroke=\"blue\" stroke-width=\"10\"/></svg>";
static lv_image_dsc_t svg_dsc;
svg_dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
svg_dsc.header.w = 450;
svg_dsc.header.h = 150;
svg_dsc.data_size = sizeof(svg_data) - 1;
svg_dsc.data = (const uint8_t *) svg_data;
lv_obj_t * svg = lv_image_create(lv_screen_active());
lv_image_set_src(svg, &svg_dsc);
}
#else
+26
View File
@@ -0,0 +1,26 @@
#include "../../lv_examples.h"
#if LV_BUILD_EXAMPLES
#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC
/**
* Load an SVG from a file
*/
void lv_example_svg_2(void)
{
lv_obj_t * svg = lv_image_create(lv_screen_active());
lv_image_set_src(svg, "A:lvgl/examples/assets/circle.svg");
}
#else
void lv_example_svg_2(void)
{
/*TODO
*fallback for online examples*/
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "SVG is not enabled");
lv_obj_center(label);
}
#endif
#endif
+36
View File
@@ -0,0 +1,36 @@
#include "../../lv_examples.h"
#if LV_BUILD_EXAMPLES
#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC
/**
* Draw SVG data in a draw event
*/
static void event_cb(lv_event_t * e)
{
static const char svg_data[] = "<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\">"
"<circle cx=\"600\" cy=\"200\" r=\"100\" fill=\"red\" stroke=\"blue\" stroke-width=\"10\"/></svg>";
lv_layer_t * layer = lv_event_get_layer(e);
lv_svg_node_t * svg = lv_svg_load_data(svg_data, sizeof(svg_data) / sizeof(char));
lv_draw_svg(layer, svg);
lv_svg_node_delete(svg);
}
void lv_example_svg_3(void)
{
lv_obj_add_event_cb(lv_screen_active(), event_cb, LV_EVENT_DRAW_MAIN, NULL);
}
#else
void lv_example_svg_3(void)
{
/*TODO
*fallback for online examples*/
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "SVG is not enabled");
lv_obj_center(label);
}
#endif
#endif
+2 -2
View File
@@ -12,8 +12,8 @@
#if LV_USE_VECTOR_GRAPHIC
#if !((LV_USE_DRAW_SW && LV_USE_THORVG) || LV_USE_DRAW_VG_LITE)
#error "LV_USE_VECTOR_GRAPHIC requires either (LV_USE_DRAW_SW and LV_USE_THORVG) or LV_USE_DRAW_VG_LITE"
#if !((LV_USE_DRAW_SW && LV_USE_THORVG) || LV_USE_DRAW_VG_LITE || (LV_USE_NEMA_GFX && LV_USE_NEMA_VG))
#error "LV_USE_VECTOR_GRAPHIC requires (LV_USE_DRAW_SW and LV_USE_THORVG) or LV_USE_DRAW_VG_LITE or (LV_USE_NEMA_GFX and LV_USE_NEMA_VG)"
#endif
#include "../misc/lv_ll.h"
+14 -3
View File
@@ -243,12 +243,18 @@ static int32_t nema_gfx_evaluate(lv_draw_unit_t * draw_unit, lv_draw_task_t * ta
}
break;
}
#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG
case LV_DRAW_TASK_TYPE_VECTOR: {
if(task->preference_score > 80) {
task->preference_score = 80;
task->preferred_draw_unit_id = DRAW_UNIT_ID_NEMA_GFX;
}
return 1;
}
#endif
case LV_DRAW_TASK_TYPE_BOX_SHADOW:
case LV_DRAW_TASK_TYPE_MASK_RECTANGLE:
case LV_DRAW_TASK_TYPE_MASK_BITMAP:
#if LV_USE_VECTOR_GRAPHIC
case LV_DRAW_TASK_TYPE_VECTOR:
#endif
default:
break;
}
@@ -328,6 +334,11 @@ static void nema_gfx_execute_drawing(lv_draw_nema_gfx_unit_t * u)
case LV_DRAW_TASK_TYPE_BORDER:
lv_draw_nema_gfx_border(t, t->draw_dsc, &t->area);
break;
#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG
case LV_DRAW_TASK_TYPE_VECTOR:
lv_draw_nema_gfx_vector(t, t->draw_dsc, &t->area);
break;
#endif
default:
break;
}
+5
View File
@@ -110,6 +110,11 @@ void lv_draw_nema_gfx_border(lv_draw_task_t * t, const lv_draw_border_dsc_t * ds
void lv_draw_nema_gfx_arc(lv_draw_task_t * t, const lv_draw_arc_dsc_t * dsc,
const lv_area_t * coords);
#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG
void lv_draw_nema_gfx_vector(lv_draw_task_t * t, const lv_draw_vector_dsc_t * dsc,
const lv_area_t * coords);
#endif
/**********************
* MACROS
+161
View File
@@ -0,0 +1,161 @@
/**
* @file lv_draw_nema_gfx_vector.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_nema_gfx.h"
#if LV_USE_NEMA_GFX && LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG
#include "lv_nema_gfx_path.h"
#include "../lv_draw_vector_private.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef struct {
lv_draw_nema_gfx_unit_t * u;
float rel_translate_x;
float rel_translate_y;
} ctx_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void task_draw_cb(void * ctx, const lv_vector_path_t * path, const lv_vector_path_ctx_t * dsc);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_draw_nema_gfx_vector(lv_draw_task_t * t, const lv_draw_vector_dsc_t * dsc,
const lv_area_t * coords)
{
ctx_t c;
c.u = (lv_draw_nema_gfx_unit_t *) t->draw_unit;
lv_layer_t * layer = t->target_layer;
lv_area_t rel_clip_area;
lv_area_copy(&rel_clip_area, &t->clip_area);
lv_area_move(&rel_clip_area, -layer->buf_area.x1, -layer->buf_area.y1);
c.rel_translate_x = -layer->buf_area.x1;
c.rel_translate_y = -layer->buf_area.y1;
nema_set_clip(rel_clip_area.x1, rel_clip_area.y1, lv_area_get_width(&rel_clip_area),
lv_area_get_height(&rel_clip_area));
lv_color_format_t dst_cf = layer->draw_buf->header.cf;
uint32_t dst_nema_cf = lv_nemagfx_cf_to_nema(dst_cf);
/* the stride should be computed internally for NEMA_TSC images and images missing a stride value */
int32_t stride = (dst_cf >= LV_COLOR_FORMAT_NEMA_TSC_START && dst_cf <= LV_COLOR_FORMAT_NEMA_TSC_END) ?
-1 : lv_area_get_width(&(layer->buf_area)) * lv_color_format_get_size(dst_cf);
nema_bind_dst_tex((uintptr_t)NEMA_VIRT2PHYS(layer->draw_buf->data), lv_area_get_width(&(layer->buf_area)),
lv_area_get_height(&(layer->buf_area)), dst_nema_cf, stride);
nema_vg_set_blend(NEMA_BL_SRC_OVER | NEMA_BLOP_SRC_PREMULT);
lv_vector_for_each_destroy_tasks(dsc->task_list, task_draw_cb, &c);
}
/**********************
* STATIC FUNCTIONS
**********************/
static void task_draw_cb(void * ctx, const lv_vector_path_t * path, const lv_vector_path_ctx_t * dsc)
{
ctx_t * c = ctx;
lv_draw_nema_gfx_unit_t * u = c->u;
if(!path) return;
nema_vg_path_clear(u->path);
nema_vg_paint_clear(u->paint);
nema_vg_paint_set_type(u->paint, NEMA_VG_PAINT_COLOR);
lv_matrix_t matrix = dsc->matrix;
matrix.m[0][2] += c->rel_translate_x;
matrix.m[1][2] += c->rel_translate_y;
nema_vg_path_set_matrix(c->u->path, (void *) &matrix);
/* the path ops array needs to be translated to nema's opcodes */
lv_vector_path_op_t * ops = lv_array_front(&path->ops);
uint32_t op_count = lv_array_size(&path->ops);
uint8_t * nema_ops = lv_malloc(op_count * sizeof(*nema_ops));
LV_ASSERT_MALLOC(nema_ops);
for(uint32_t i = 0; i < op_count; i++) {
nema_ops[i] = ops[i] == LV_VECTOR_PATH_OP_MOVE_TO ? NEMA_VG_PRIM_MOVE :
ops[i] == LV_VECTOR_PATH_OP_LINE_TO ? NEMA_VG_PRIM_LINE :
ops[i] == LV_VECTOR_PATH_OP_QUAD_TO ? NEMA_VG_PRIM_BEZIER_QUAD :
ops[i] == LV_VECTOR_PATH_OP_CUBIC_TO ? NEMA_VG_PRIM_BEZIER_CUBIC :
/*LV_VECTOR_PATH_OP_CLOSE*/ NEMA_VG_PRIM_CLOSE;
}
/* the path points array is in the right format for nema to use as-is */
uint32_t point_count = lv_array_size(&path->points);
float * points = lv_array_front(&path->points);
nema_vg_path_set_shape(u->path, op_count, nema_ops, point_count, points);
nema_vg_set_quality(path->quality == LV_VECTOR_PATH_QUALITY_LOW ? NEMA_VG_QUALITY_FASTER :
path->quality == LV_VECTOR_PATH_QUALITY_MEDIUM ? NEMA_VG_QUALITY_BETTER :
/*LV_VECTOR_PATH_QUALITY_HIGH*/ NEMA_VG_QUALITY_MAXIMUM);
if(dsc->fill_dsc.opa) {
nema_vg_set_fill_rule(dsc->fill_dsc.fill_rule == LV_VECTOR_FILL_NONZERO ? NEMA_VG_FILL_NON_ZERO :
/*LV_VECTOR_FILL_EVENODD*/ NEMA_VG_FILL_EVEN_ODD);
uint32_t nema_dsc_color = nema_rgba(dsc->fill_dsc.color.red, dsc->fill_dsc.color.green,
dsc->fill_dsc.color.blue, dsc->fill_dsc.color.alpha);
nema_vg_paint_set_paint_color(u->paint, nema_dsc_color);
nema_vg_draw_path(u->path, u->paint);
}
if(dsc->stroke_dsc.opa) {
nema_vg_set_fill_rule(NEMA_VG_STROKE);
nema_vg_stroke_set_width(dsc->stroke_dsc.width);
uint8_t cap = dsc->stroke_dsc.cap == LV_VECTOR_STROKE_CAP_BUTT ? NEMA_VG_CAP_BUTT :
dsc->stroke_dsc.cap == LV_VECTOR_STROKE_CAP_SQUARE ? NEMA_VG_CAP_SQUARE :
/*LV_VECTOR_STROKE_CAP_ROUND*/ NEMA_VG_CAP_ROUND;
nema_vg_stroke_set_cap_style(cap, cap);
uint8_t join = dsc->stroke_dsc.join == LV_VECTOR_STROKE_JOIN_MITER ? NEMA_VG_JOIN_MITER :
dsc->stroke_dsc.join == LV_VECTOR_STROKE_JOIN_BEVEL ? NEMA_VG_JOIN_BEVEL :
/*LV_VECTOR_STROKE_JOIN_ROUND*/ NEMA_VG_JOIN_ROUND;
nema_vg_stroke_set_join_style(join);
nema_vg_stroke_set_miter_limit(dsc->stroke_dsc.miter_limit);
uint32_t nema_dsc_color = nema_rgba(dsc->stroke_dsc.color.red, dsc->stroke_dsc.color.green,
dsc->stroke_dsc.color.blue, dsc->stroke_dsc.color.alpha);
nema_vg_paint_set_paint_color(u->paint, nema_dsc_color);
nema_vg_draw_path(u->path, u->paint);
}
nema_cl_submit(&c->u->cl);
nema_cl_wait(&u->cl);
lv_free(nema_ops);
}
#endif /*LV_USE_NEMA_GFX && LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG*/