How to improve the fps?

I have tried different configuration on my Raspberry Pi 4 (4G). And I found that the Widgets demo is always the big problem.

The FPS drops to 20 (Benchmark Results shows the avg is 70 ), when scrolling the second tab (Analytics)in Widgets demo .

In my project ,the page is more complicated than the Widgets demo, so how to improve the fps ?

Here is a Performance Analysis from gpt:

LVGL Benchmark Results

Test Environment

  • Hardware: Raspberry Pi 4 (4G)
  • System: Linux (raspberrypi)
  • Screen Resolution: 1280x800 @ 59Hz (HDMI-A-1)
  • LVGL Version: 9.6.0 dev
  • Build Configuration: [configs/get_started.defconfig](file:///lv_port_linux/configs/get_started.defconfig)
    • Backend: DRM + EGL (GBM surface)
    • Render Engine: NanoVG (GLES2 backend)
    • LV_DRAW_SW_DRAW_UNIT_CNT=2 (but NanoVG backend forces tile_cnt=1, does not participate in rendering)
    • LV_DEF_REFR_PERIOD=8 ms
    • VSync: Disabled (eglSwapInterval(0))
  • Run Command: sudo ./build/bin/lvglsim -D benchmark

Key Code Changes

To resolve the NanoVG backend black-screen issue, tile_cnt=1 is enforced at [lv_linux_drm_egl.c:131-135](file:///home/pi/Desktop/lvgl95/lv_port_linux/lvgl/src/drivers/display/drm/lv_linux_drm_egl.c#L131-L135):

#if LV_USE_DRAW_NANOVG
    /* NanoVG renders the full frame directly to the EGL surface's default
     * framebuffer. Tiling would create child layers with FBOs, causing
     * NanoVG to render off-screen instead of to the visible surface. */
    lv_display_set_tile_cnt(display, 1);
#endif

Benchmark Results

Name Avg. CPU Avg. FPS Avg. time render time flush time
Empty screen 13% 84 21 10 11
Moving wallpaper 15% 63 14 0 14
Single rectangle 11% 81 11 0 11
Multiple rectangles 11% 82 11 0 11
Multiple RGB images 14% 83 10 0 10
Multiple ARGB images 10% 82 10 0 10
Rotated ARGB images 14% 87 10 0 10
Multiple labels 26% 87 9 5 4
Screen sized text 33% 46 20 20 0
Multiple arcs 12% 51 18 2 16
Containers 20% 43 20 7 13
Containers with overlay 18% 39 23 7 16
Containers with opa 18% 43 21 8 13
Containers with opa_layer 18% 13 70 43 27
Containers with scrolling 23% 42 22 10 12
Widgets demo 14% 73 15 5 10
All scenes avg. 16% 62 18 7 11

Performance Analysis

Overall Performance

  • Average 62 FPS, CPU 16% — Smooth overall, usable
  • render 7ms / flush 11ms — Flush takes the majority; the bottleneck is GPU compositing + eglSwapBuffers

Excellent Performance (>80 FPS)

Empty screen / Single rectangle / Multiple rectangles / Multiple RGB/ARGB images / Rotated ARGB images / Multiple labels — Pure GPU drawing that NanoVG excels at, nearly hitting the refresh rate limit.

Performance Bottleneck Scenarios

Scenario FPS Problem Analysis
Containers with opa_layer 13 Worst. opa_layer requires NanoVG to allocate FBO for off-screen rendering then composite; layer creation/readback overhead is huge (render 43ms + flush 27ms)
Screen sized text 46 Large text blocks. NanoVG text rendering tessellates per glyph, CPU 33% is high
Containers (overlay/opa/scrolling) 39~43 Containers + semi-transparent overlay trigger multiple layer compositions, flush 13~16ms
Multiple arcs 51 Arc path tessellation is heavy, flush 16ms
Moving wallpaper 63 Large image displacement, flush 14ms (render 0ms indicates pure blit)

Optimization Suggestions (Sorted by Gain)

  1. opa_layer scenario (largest bottleneck): Check the FBO reuse logic in [lv_draw_nanovg_layer.c](file:///home/pi/Desktop/lvgl95/lv_port_linux/lvgl/src/draw/nanovg/lv_draw_nanovg_layer.c) to confirm whether the FBO cache is hit and reused every frame; missing the cache is extremely costly.

  2. High flush ratio: Enable eglSwapInterval(1) to enable VSync (currently 0), reducing invalid refreshes to 60Hz, lowering CPU/GPU usage and avoiding tearing. Modify eglSwapInterval(ctx->egl_display, 0) in [lv_opengles_egl.c](file:///home/pi/Desktop/lvgl95/lv_port_linux/lvgl/src/drivers/opengles/lv_opengles_egl.c).

  3. Text rendering: Pre-generate glyph atlas (NanoVG’s nvgCreateFontMem already supports this), confirm the font is not reloaded every frame.

  4. Arc optimization: Check whether the SW fallback for arcs is enabled; a pure GPU path should be faster.

Conclusion

Current performance is reasonable for Raspberry Pi + NanoVG. If you don’t need semi-transparent composition effects like opa_layer, the overall experience is already smooth. For further optimization, prioritize FBO cache hit rate and VSync configuration.


Config B: OpenGL ES Draw Unit + tile_cnt=2 (2026-07-31)

Test Environment

  • Hardware/System/Screen Resolution: Same as above (Raspberry Pi, 1280x800 @ 59Hz)
  • LVGL Version: 9.6.0 dev
  • Render Engine: OpenGL ES Draw Unit (not NanoVG)
  • Backend: DRM + EGL (GBM surface)
  • Tiling: Enabled (CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=2, tile_cnt=2)
  • LV_DEF_REFR_PERIOD=8 ms, VSync disabled

Key Code Changes

To support the OpenGL ES Draw Unit under tiling mode, in [lv_draw_opengles.c dispatch()](file:///home/pi/Desktop/lvgl95/lv_port_linux/lvgl/src/draw/opengles/lv_draw_opengles.c#L236-L265) the tile layer reuses the display layer_head’s texture (tile layer shares draw_buf):

if(layer->draw_buf == disp->layer_head->draw_buf) {
    texture = (unsigned int)(uintptr_t)disp->layer_head->user_data;
}
else {
    int32_t w = lv_area_get_width(&layer->buf_area);
    int32_t h = lv_area_get_height(&layer->buf_area);
    texture = create_texture(w, h, NULL);
}

The tile_cnt=1 restriction in [lv_linux_drm_egl.c](file:///home/pi/Desktop/lvgl95/lv_port_linux/lvgl/src/drivers/display/drm/lv_linux_drm_egl.c#L131) is now applied only to NanoVG; the OpenGL ES backend takes the native tiling path.

Benchmark Results

Name Avg. CPU Avg. FPS Avg. time render time flush time
Empty screen 16% 110 0 0 0
Moving wallpaper 30% 98 9 6 3
Single rectangle 7% 123 0 0 0
Multiple rectangles 15% 123 0 0 0
Multiple RGB images 10% 123 1 1 0
Multiple ARGB images 12% 123 1 1 0
Rotated ARGB images 10% 122 0 0 0
Multiple labels 21% 122 3 3 0
Screen sized text 13% 122 3 1 2
Multiple arcs 24% 118 4 4 0
Containers 9% 123 0 0 0
Containers with overlay 23% 101 8 6 2
Containers with opa 10% 123 0 0 0
Containers with opa_layer 10% 123 1 1 0
Containers with scrolling 29% 97 8 8 0
Widgets demo 22% 57 28 27 1
All scenes avg. 16% 113 3 3 0

Performance Analysis

  • Average 113 FPS (CPU 16%) — +82% over NanoVG (62 FPS), excellent overall performance
  • render 3ms / flush 0ms — Flush is nearly zero, the bottleneck is completely eliminated

Comparison with NanoVG Config A

Scenario NanoVG (FPS) OpenGL ES tile=2 (FPS) Gain
All scenes avg. 62 113 +82%
Containers with opa_layer 13 123 +846% (largest improvement)
Screen sized text 46 122 +165%
Containers with opa 43 123 +186%
Multiple arcs 51 118 +131%
Containers with scrolling 42 97 +131%
Empty screen 84 110 +31%
Widgets demo 73 57 -22% (only regression)

Key Findings

  1. opa_layer 13 → 123 FPS: The OpenGL ES draw unit uses glFramebufferTexture2D to directly reuse the layer_head texture, bypassing NanoVG’s FBO create/readback path; the bottleneck is completely resolved
  2. Text rendering greatly improved: 13% CPU (vs NanoVG 33%); the OpenGL ES draw unit uses draw_to_texture + SW subtask to efficiently cache glyphs
  3. Widgets demo regresses 22%: 73 → 57 FPS, suspected that some complex components (tables/charts) in this demo don’t use the cache path under the OpenGL ES draw unit; render 27ms dominates, needs further investigation
  4. Flush time drops to zero: The OpenGL ES draw unit renders directly to the display texture; flush_cb only does eglSwapBuffers, with almost no overhead

Config C: NanoVG (GLES2) + tile_cnt=2 (2026-07-31)

Configuration

  • Backend: NanoVG GLES2 (CONFIG_LV_USE_DRAW_NANOVG=y, CONFIG_LV_NANOVG_BACKEND_GLES2=y)
  • tile_cnt=2 (CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=2, the tile_cnt=1 restriction in lv_linux_drm_egl.c is disabled)
  • Code change: In lv_draw_nanovg.c CHILD_CREATED, for tile layers (parent==NULL), skip FBO allocation, keep user_data=NULL, letting NanoVG render to the default framebuffer
  • Build: Release (-O3 -DNDEBUG)
  • Screen: 1280x800 (HDMI)
  • Letter cache: 2048, FBO cache: 4
  • LV_DEF_REFR_PERIOD: 8ms

Test Results

Name Avg. CPU Avg. FPS Avg. time render time flush time
Empty screen 16% 65 12 2 10
Moving wallpaper 16% 48 19 3 16
Single rectangle 14% 72 13 1 12
Multiple rectangles 13% 56 15 1 14
Multiple RGB images 11% 65 13 1 12
Multiple ARGB images 13% 60 14 2 12
Rotated ARGB images 13% 70 12 1 11
Multiple labels 20% 68 12 6 6
Screen sized text 30% 39 23 23 0
Multiple arcs 14% 43 20 3 17
Containers 17% 37 24 8 16
Containers with overlay 13% 31 29 6 23
Containers with opa 18% 39 24 8 16
Containers with opa_layer 20% 21 43 25 18
Containers with scrolling 19% 36 25 11 14
Widgets demo 13% 57 26 9 17
All scenes avg. 16% 50 19 6 13

Three-Config Comparison

Metric Config A: NanoVG tile=1 Config B: OpenGL ES tile=2 Config C: NanoVG tile=2
All scenes avg. FPS 62 113 50
opa_layer FPS 13 123 21
Screen sized text 46 122 39
Widgets demo 73 57 57
flush time 11ms 0ms 13ms
render time 8ms 3ms 6ms

Key Findings

  1. NanoVG + tile=2 actually degrades performance: Average 50 FPS (vs 62 FPS at tile=1), mainly due to flush time rising from 11ms to 13ms
  2. Flush time rises significantly: 13ms (vs 11ms at tile=1, 0ms at OpenGL ES tile=2); tile layers sharing the framebuffer causes extra sync/invalidation overhead
  3. opa_layer slightly improved but still poor: 21 FPS (vs 13 FPS at tile=1), because tiling offloads some non-opa_layer regions
  4. Widgets demo flat: 57 FPS, same as OpenGL ES tile=2, possibly limited by other factors
  5. Conclusion: The NanoVG architecture is not suitable for the tile-shared framebuffer mode; performance drops instead of improving. OpenGL ES tile=2 remains the optimal configuration

Config D: OpenGL ES Draw Unit + tile_cnt=4 (2026-07-31)

Configuration

  • Backend: OpenGL ES Draw Unit (CONFIG_LV_USE_DRAW_OPENGLES=y)
  • tile_cnt=4 (CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=4)
  • Build: Release (-O3 -DNDEBUG)
  • Screen: 1280x800 (HDMI)
  • Other: Same as Config B

Test Results

Name Avg. CPU Avg. FPS Avg. time render time flush time
Empty screen 27% 110 1 1 0
Moving wallpaper 23% 108 8 4 4
Single rectangle 7% 123 0 0 0
Multiple rectangles 11% 122 0 0 0
Multiple RGB images 13% 123 1 1 0
Multiple ARGB images 13% 123 1 1 0
Rotated ARGB images 10% 123 0 0 0
Multiple labels 25% 123 3 3 0
Screen sized text 11% 119 3 1 2
Multiple arcs 25% 116 5 5 0
Containers 9% 123 0 0 0
Containers with overlay 25% 96 9 8 1
Containers with opa 12% 123 0 0 0
Containers with opa_layer 10% 123 1 1 0
Containers with scrolling 39% 87 10 10 0
Widgets demo 22% 54 31 30 1
All scenes avg. 17% 112 4 4 0

Config B vs D Comparison (OpenGL ES tile=2 vs tile=4)

Metric B: tile=2 D: tile=4 Difference
All scenes avg. FPS 113 112 -1
All scenes avg. CPU 16% 17% +1%
render time 3ms 4ms +1ms
flush time 0ms 0ms flat
Empty screen CPU 16% 27% +11% (significantly higher)
Moving wallpaper CPU 30% 23% -7%
Multiple labels CPU 21% 25% +4%
Screen sized text 122 FPS / 13% CPU 119 FPS / 11% CPU -3 FPS / -2% CPU
Containers with scrolling 97 FPS / 29% CPU 87 FPS / 39% CPU -10 FPS / +10% CPU (clear regression)
Widgets demo 57 FPS / 27ms render 54 FPS / 30ms render -3 FPS (slower render)

Key Findings

  1. tile=4 yields no overall benefit: Average 112 FPS vs 113 FPS at tile=2, essentially flat
  2. CPU usage rises: 17% vs 16%, layer management overhead increases
  3. Some scenarios clearly regress:
    • Empty screen CPU +11% (tile scheduling overhead dominates in empty scenes)
    • Containers with scrolling -10 FPS / +10% CPU (frequent tile switching in scrolling scenes)
    • Widgets demo -3 FPS (render 30ms vs 27ms, slower)
  4. Few scenarios slightly improve: Moving wallpaper CPU -7%, but only +10 FPS
  5. Confirms previous analysis: The OpenGL ES draw unit is a single-instance serial GPU pipeline; more tiles cannot parallelize, and instead add layer scheduling overhead
  6. Conclusion: tile=2 is the optimal value for the OpenGL ES draw unit; increasing further is not recommended

Config E: OpenGL ES Draw Unit + tile=2 + REFR_PERIOD=5ms (2026-07-31)

Configuration

  • Backend: OpenGL ES Draw Unit (CONFIG_LV_USE_DRAW_OPENGLES=y)
  • tile_cnt=2 (CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=2)
  • Refresh period: 5ms (CONFIG_LV_DEF_REFR_PERIOD=5, theoretical limit 200 FPS)
  • Build: Release (-O3 -DNDEBUG)
  • Screen: 1280x800 (HDMI)
  • Other: Same as Config B

Test Results

Scenario Avg. CPU Avg. FPS Avg. time render time flush time
Empty screen 22% 151 4 1 3
Moving wallpaper 27% 99 8 6 2
Single rectangle 12% 190 4 0 4
Multiple rectangles 15% 168 4 0 4
Multiple RGB images 14% 183 3 0 3
Multiple ARGB images 22% 179 3 0 3
Rotated ARGB images 12% 194 1 0 1
Multiple labels 26% 151 4 3 1
Screen sized text 15% 130 6 1 5
Multiple arcs 27% 159 4 3 1
Containers 12% 194 0 0 0
Containers with overlay 24% 101 7 5 2
Containers with opa 12% 196 0 0 0
Containers with opa_layer 13% 181 3 1 2
Containers with scrolling 29% 100 8 8 0
Widgets demo 24% 79 28 27 1
All scenes avg. 19% 153 5 3 2

Config B vs E Comparison (REFR_PERIOD 8ms vs 5ms)

Metric B: 8ms E: 5ms Difference
All scenes avg. FPS 113 153 +40 (+35%) :white_check_mark:
All scenes avg. CPU 16% 19% +3%
Empty screen 110 FPS 151 FPS +41
Single rectangle 123 FPS 190 FPS +67
Multiple rectangles 123 FPS 168 FPS +45
Rotated ARGB images 122 FPS 194 FPS +72
Containers 123 FPS 194 FPS +71
Containers with opa 123 FPS 196 FPS +73
Containers with opa_layer 123 FPS 181 FPS +58
Multiple arcs 118 FPS 159 FPS +41
Multiple labels 122 FPS 151 FPS +29
Screen sized text 122 FPS 130 FPS +8
Moving wallpaper 98 FPS 99 FPS +1 (GPU bottleneck)
Containers with overlay 101 FPS 101 FPS 0 (GPU bottleneck)
Containers with scrolling 97 FPS 100 FPS +3
Widgets demo 57 FPS 79 FPS +22 :white_check_mark:
flush time (avg) 0ms 2ms +2ms

Key Findings

  1. Average FPS significantly improved: 113 → 153 (+35%); REFR_PERIOD=5ms removed the 8ms-period FPS ceiling
  2. Light-load scenarios improve significantly: Simple scenes approach the theoretical 200 FPS limit (Single rectangle 190, Containers 194)
  3. Heavy-load scenarios are GPU-limited:
    • Moving wallpaper 99 FPS (render 6ms, GPU saturated)
    • Containers with overlay 101 FPS (render 5ms + flush 2ms)
    • These two scenarios are bottlenecked by render time; shortening REFR_PERIOD does not help
  4. Widgets demo unexpectedly improves: 57 → 79 FPS (+22), possibly because more frequent refresh scheduling reduces inter-frame idle time
  5. CPU usage rises: 16% → 19% (+3%), more frequent refresh brings additional scheduling overhead
  6. Flush time increases: 0ms → 2ms, more frequent EGL swaps introduce some overhead
  7. Conclusion: REFR_PERIOD=5ms is an effective optimization direction, improving overall FPS by 35% at the cost of +3% CPU

Hi @LLLLL72 , thank you to open this topic.

Well, the FPS drop during scrolling at that resolution is likely due to the large pixel requiring significant memory bandwidth and CPU rendering time. So, I recommend a feel thinks to improve the FPS. Open your lv_conf.h

- Reduce refresh period(#define LV_DEF_REFR_PERIOD 16)

  • Double Buffering (#define LV_USE_OS LV_OS_PTHREAD)
  • Multi-threading (#define LV_LINUX_FBDEV_BUFFER_COUNT 2)

Thanks for your reply .
As I know, opengl has its own double buffer in the driver. And I have already try to set the period to 8ms and 5ms. Here is the comparision:

Metric B: 8ms E: 5ms Difference
All scenes avg. FPS 113 153 +40 (+35%) :white_check_mark:

The problem is that the FPS drops to 20 while scrolling the widget.

Hi @LLLLL72

I don’t know the Raspberry Pi hardware, but on my own developments with other platforms to copy rectangles of video buffer around I have always used spare DMA channels to create ‘hardware based’ memory copy functions rather than using a soft memcpy(). If the Raspberry Pi has any such free channels you could potentially use them to perform all buffer to buffer transactions at full memory speed.

Just a thought…

Cheers,

Pete