Skip to content

feat(chart): crowded-mode rendering on opengles support, with head/tail tint gradients#9938

Open
FishOfTheNorthStar wants to merge 9 commits intolvgl:masterfrom
FishOfTheNorthStar:opengl-crowded-chart-head-tails
Open

feat(chart): crowded-mode rendering on opengles support, with head/tail tint gradients#9938
FishOfTheNorthStar wants to merge 9 commits intolvgl:masterfrom
FishOfTheNorthStar:opengl-crowded-chart-head-tails

Conversation

@FishOfTheNorthStar
Copy link
Copy Markdown
Contributor

This is in progress work and needs more testing before merging. I'm using github's code review and build tests to confirm this code works on it's own, outside of a larger use of it and other modified code in a demo. I'm carving just this part out of that into this PR.

This PR adds support for "crowded-mode" rendering of lv_chart's on OpenGL ES targets.

It also adds the idea of a head and tail of the data, with each having it's own normalized length and color.
Screenshot from 2026-04-05 03-51-27

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/widgets/chart/lv_chart.c">

<violation number="1" location="src/widgets/chart/lv_chart.c:1168">
P2: Scissor fill mode forces crowded_mode, which triggers the large points allocation and aborts rendering on OOM even though scissor fill never uses line_dsc.points. This introduces an unnecessary memory dependency and failure path for scissor fill rendering.</violation>

<violation number="2" location="src/widgets/chart/lv_chart.c:1243">
P2: Head/tail gradient distance is computed using `p_act` before it’s updated for the current loop index, so the tint/size effects are based on the previous point and shift by one sample.</violation>
</file>

<file name="src/widgets/chart/lv_chart.h">

<violation number="1" location="src/widgets/chart/lv_chart.h:31">
P2: LV_USE_CHART_SCISSOR_FILL_MODE is hardcoded in the public header after lv_conf_internal.h, overriding any project configuration and forcing this feature on for all builds. This removes configurability and can force unintended code paths to compile.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/widgets/chart/lv_chart.c Outdated
Comment thread src/widgets/chart/lv_chart.c Outdated

for(i = 0; i < chart->point_cnt; i++) {
if(next_none > -1) {
active_norm = (float)p_act / (float)(chart->point_cnt);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Head/tail gradient distance is computed using p_act before it’s updated for the current loop index, so the tint/size effects are based on the previous point and shift by one sample.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/widgets/chart/lv_chart.c, line 1243:

<comment>Head/tail gradient distance is computed using `p_act` before it’s updated for the current loop index, so the tint/size effects are based on the previous point and shift by one sample.</comment>

<file context>
@@ -1093,7 +1215,35 @@ static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer)
+
         for(i = 0; i < chart->point_cnt; i++) {
+            if(next_none > -1) {
+                active_norm = (float)p_act / (float)(chart->point_cnt);
+                next_none_norm_dist = ((LV_ABS((next_none_norm - 1.f) - active_norm) < LV_ABS(next_none_norm - active_norm)) ?
+                                       (next_none_norm - 1.f) : next_none_norm) - active_norm;
</file context>
Fix with Cubic


/* Enable crowded mode rendering on Opengles output using
glScissor fills. Also enables head/tail tint gradients */
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: LV_USE_CHART_SCISSOR_FILL_MODE is hardcoded in the public header after lv_conf_internal.h, overriding any project configuration and forcing this feature on for all builds. This removes configurability and can force unintended code paths to compile.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/widgets/chart/lv_chart.h, line 31:

<comment>LV_USE_CHART_SCISSOR_FILL_MODE is hardcoded in the public header after lv_conf_internal.h, overriding any project configuration and forcing this feature on for all builds. This removes configurability and can force unintended code paths to compile.</comment>

<file context>
@@ -26,6 +26,10 @@ extern "C" {
 
+/* Enable crowded mode rendering on Opengles output using
+    glScissor fills.  Also enables head/tail tint gradients */
+#define LV_USE_CHART_SCISSOR_FILL_MODE 1
+
 /**********************
</file context>
Suggested change
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
#ifndef LV_USE_CHART_SCISSOR_FILL_MODE
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
#endif
Fix with Cubic

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Hi 👋, thank you for your PR!

We've run benchmarks in an emulated environment. Here are the results:

ARM Emulated 32b - lv_conf_perf32b

Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
All scenes avg. 27 37 7 7 0
Detailed Results Per Scene
Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
Empty screen 11 33 0 0 0
Moving wallpaper 2 33 1 1 0
Single rectangle 0 50 0 0 0
Multiple rectangles 0 33 0 0 0
Multiple RGB images 0 39 0 0 0
Multiple ARGB images 11 42 (+2) 2 (-1) 2 (-1) 0
Rotated ARGB images 52 (-3) 44 15 15 0
Multiple labels 8 (+2) 33 (-2) 0 0 0
Screen sized text 80 45 17 17 0
Multiple arcs 40 33 7 7 0
Containers 4 37 0 0 0
Containers with overlay 88 (+2) 21 44 44 0
Containers with opa 7 (-10) 38 (+1) 1 1 0
Containers with opa_layer 19 (+1) 33 (-1) 6 (+1) 6 (+1) 0
Containers with scrolling 44 45 12 12 0
Widgets demo 70 (-2) 39 (-1) 16 16 0
All scenes avg. 27 37 7 7 0

ARM Emulated 64b - lv_conf_perf64b

Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
All scenes avg. 24 37 6 6 0
Detailed Results Per Scene
Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
Empty screen 11 33 0 0 0
Moving wallpaper 1 33 0 0 0
Single rectangle 0 50 0 0 0
Multiple rectangles 0 35 0 0 0
Multiple RGB images 0 39 0 0 0
Multiple ARGB images 11 42 0 0 0
Rotated ARGB images 29 33 9 9 0
Multiple labels 2 35 0 0 0
Screen sized text 84 46 18 18 0
Multiple arcs 31 33 6 6 0
Containers 4 37 0 0 0
Containers with overlay 89 22 41 41 0
Containers with opa 14 38 1 1 0
Containers with opa_layer 6 36 1 1 0
Containers with scrolling 47 47 (-1) 12 12 0
Widgets demo 67 40 15 15 0
All scenes avg. 24 37 6 6 0

Disclaimer: These benchmarks were run in an emulated environment using QEMU with instruction counting mode.
The timing values represent relative performance metrics within this specific virtualized setup and should
not be interpreted as absolute real-world performance measurements. Values are deterministic and useful for
comparing different LVGL features and configurations, but may not correlate directly with performance on
physical hardware. The measurements are intended for comparative analysis only.


🤖 This comment was automatically generated by a bot.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/widgets/chart/lv_chart.c">

<violation number="1" location="src/widgets/chart/lv_chart.c:1235">
P2: Using `lv_value_precise_t` for head/tail normalization introduces integer-division behavior when `LV_USE_FLOAT=0`, breaking smooth crowded-mode gradient/tint transitions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/widgets/chart/lv_chart.c Outdated
@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai bot commented Apr 6, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@FASTSHIFT FASTSHIFT changed the title feat: lv_chart crowded-mode rendering on opengles support, with head/tail tint gradients feat(chart): crowded-mode rendering on opengles support, with head/tail tint gradients Apr 7, 2026
@FASTSHIFT FASTSHIFT requested a review from Copilot April 7, 2026 02:59
@FASTSHIFT
Copy link
Copy Markdown
Collaborator

Can you provide Tests, Documentation, and Examples for new features?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional OpenGL ES–oriented “crowded mode” rendering path for lv_chart line series, along with configurable “head” and “tail” tint/size effects intended to highlight the newest/oldest data regions.

Changes:

  • Introduces a new compile-time feature flag and public APIs for forcing crowded rendering and configuring head/tail effects.
  • Implements an alternate crowded rendering path that uses per-column fill tasks (intended to map to glScissor-style fills on OpenGL ES).
  • Extends the internal lv_chart_t instance data with head/tail and forced-crowded configuration fields.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
src/widgets/chart/lv_chart.h Adds a feature flag definition and public setter/getter APIs for crowded/head/tail behavior.
src/widgets/chart/lv_chart.c Implements the new APIs and modifies line-series rendering to support the scissor-fill crowded mode with head/tail tinting.
src/widgets/chart/lv_chart_private.h Adds new per-chart state fields (force crowded, head/tail params/colors) guarded by the new feature flag.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +30 to +31
glScissor fills. Also enables head/tail tint gradients */
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LV_USE_CHART_SCISSOR_FILL_MODE is being hard-defined to 1 in a public widget header, which forces the feature on for all builds (and for all render backends) and bypasses the normal LVGL configuration mechanism. This should be a user-configurable LV_USE_* option provided via lv_conf.h/Kconfig (and surfaced through lv_conf_internal.h), ideally defaulting to 0 and/or depending on LV_USE_DRAW_OPENGLES.

Suggested change
glScissor fills. Also enables head/tail tint gradients */
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
glScissor fills. Also enables head/tail tint gradients.
* This option should normally come from lv_conf.h/lv_conf_internal.h.
* Only provide a backend-aware fallback here when it is not configured.
*/
#ifndef LV_USE_CHART_SCISSOR_FILL_MODE
#ifdef LV_USE_DRAW_OPENGLES
#if LV_USE_DRAW_OPENGLES
#define LV_USE_CHART_SCISSOR_FILL_MODE 1
#else
#define LV_USE_CHART_SCISSOR_FILL_MODE 0
#endif
#else
#define LV_USE_CHART_SCISSOR_FILL_MODE 0
#endif
#endif

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +197
* Set the tail power
* @param obj pointer to a chart object
* @param norm set a 0 to 4096 value representing how sharply the tail effect should taper in, 1024 = linear
*/
//void lv_chart_set_tail_pow(lv_obj_t * obj, uint32_t norm);

/**
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a commented-out public API declaration (//void lv_chart_set_tail_pow(...)). Public headers shouldn’t contain dead/commented APIs because it becomes an accidental part of the interface and creates ambiguity for users. Either implement and document the API, or remove the commented declaration until it’s ready.

Suggested change
* Set the tail power
* @param obj pointer to a chart object
* @param norm set a 0 to 4096 value representing how sharply the tail effect should taper in, 1024 = linear
*/
//void lv_chart_set_tail_pow(lv_obj_t * obj, uint32_t norm);
/**

Copilot uses AI. Check for mistakes.
Comment on lines +204 to +210
/**
* Get the tail color
* @param obj pointer to a chart object
* @param color set the color of the line at the tip of the tail
*/
void lv_chart_set_tail_color(lv_obj_t * obj, lv_color_t color);

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring for lv_chart_set_tail_color incorrectly says “Get the tail color” and has an extra @param color description under a “Get” heading. Update the comment to describe setting the tail color to match the function name and parameters.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +78
#if LV_USE_CHART_SCISSOR_FILL_MODE
bool force_crowded_mode;
uint32_t head_percent;
int32_t head_size_offset;
lv_color_t head_color;

uint32_t tail_percent;
int32_t tail_size_offset;
lv_color_t tail_color;
#endif
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New fields are added under LV_USE_CHART_SCISSOR_FILL_MODE, but lv_chart_constructor doesn’t initialize them. They are later read during rendering (e.g., force_crowded_mode, head_percent, colors), which can lead to undefined behavior and non-deterministic output. Please add explicit defaults in the constructor when this feature is enabled.

Copilot uses AI. Check for mistakes.
Comment on lines +259 to +267
void lv_chart_set_head_percent(lv_obj_t * obj, uint32_t norm)
{
LV_ASSERT_OBJ(obj, MY_CLASS);

lv_chart_t * chart = (lv_chart_t *)obj;
if(chart->head_percent == norm) return;

chart->head_percent = norm;
lv_obj_invalidate(obj);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setters accept normalized ranges documented as 0..1024 (head/tail percent), but they currently store the value without clamping/validation. This can produce unexpected behavior (e.g., >100% head/tail regions) and makes the API contract inaccurate. Consider clamping to the documented range (and documenting what happens at the extremes).

Copilot uses AI. Check for mistakes.
Comment on lines +1327 to +1332
coords.x1 = (int32_t)p_x;
coords.x2 = (int32_t)p_x + ex_size;
coords.y1 = (int32_t)y_min;
coords.y2 = (int32_t)y_max + ex_size;
if((int32_t)y_min == (int32_t)y_max) {
coords.y2 = (int32_t)y_min + 1;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ex_size can become <= 0 when head_size_offset/tail_size_offset are negative, which can produce invalid areas (x2 < x1/y2 < y1) passed to lv_draw_add_task. Also, lv_area_t coordinates are inclusive; coords.x2 = p_x + ex_size makes the filled width ex_size + 1. Clamp ex_size to at least 1 and compute x2/y2 using inclusive semantics (e.g., x2 = x1 + ex_size - 1).

Suggested change
coords.x1 = (int32_t)p_x;
coords.x2 = (int32_t)p_x + ex_size;
coords.y1 = (int32_t)y_min;
coords.y2 = (int32_t)y_max + ex_size;
if((int32_t)y_min == (int32_t)y_max) {
coords.y2 = (int32_t)y_min + 1;
if(ex_size < 1) {
ex_size = 1;
}
coords.x1 = (int32_t)p_x;
coords.x2 = (int32_t)p_x + ex_size - 1;
coords.y1 = (int32_t)y_min;
coords.y2 = (int32_t)y_max + ex_size - 1;
if((int32_t)y_min == (int32_t)y_max) {
coords.y2 = (int32_t)y_min + ex_size - 1;

Copilot uses AI. Check for mistakes.
Comment on lines +1342 to +1347
if(in_head) {
outcol = lv_color_mix(chart->head_color, outcol, (int8_t)(norm_edge_effect * 255.0));
}
else if(in_tail) {
outcol = lv_color_mix(chart->tail_color, outcol, (int8_t)(norm_edge_effect * 255.0));

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lv_color_mix takes a uint8_t mix ratio (0..255), but the code casts to int8_t. Values >127 will wrap negative and lead to incorrect colors. Use an unsigned type (uint8_t/lv_opa_t) and clamp/round norm_edge_effect * 255 into [0,255] before calling lv_color_mix.

Suggested change
if(in_head) {
outcol = lv_color_mix(chart->head_color, outcol, (int8_t)(norm_edge_effect * 255.0));
}
else if(in_tail) {
outcol = lv_color_mix(chart->tail_color, outcol, (int8_t)(norm_edge_effect * 255.0));
if(in_head || in_tail) {
lv_value_precise_t mix_value = norm_edge_effect * (lv_value_precise_t)255.0;
if(mix_value < (lv_value_precise_t)0.0) {
mix_value = (lv_value_precise_t)0.0;
}
else if(mix_value > (lv_value_precise_t)255.0) {
mix_value = (lv_value_precise_t)255.0;
}
lv_opa_t mix = (lv_opa_t)(mix_value + (lv_value_precise_t)0.5);
if(in_head) {
outcol = lv_color_mix(chart->head_color, outcol, mix);
}
else {
outcol = lv_color_mix(chart->tail_color, outcol, mix);
}

Copilot uses AI. Check for mistakes.
}
#endif
bg_dsc->color = outcol;
bg_dsc->opa = LV_OPA_COVER;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In scissor-fill mode the fill descriptor forces bg_dsc->opa = LV_OPA_COVER, which ignores the chart’s line opacity/style (line_dsc.opa) and can change appearance compared to the normal path. Set the fill task opacity consistently with the line descriptor (and consider whether other style attributes like line width should be reflected too).

Suggested change
bg_dsc->opa = LV_OPA_COVER;
bg_dsc->opa = line_dsc.opa;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants