kdschlosser / lvgl_micropython

LVGL module for MicroPython
MIT License
42 stars 11 forks source link

Glitchy double-buffered display updates (ESP32-S3 w/RGB ST7262 LCD) #33

Open bland328 opened 2 months ago

bland328 commented 2 months ago

I'm unable to determine why display updates often flash and glitch, even though I'm (hopefully appropriately) using double buffering.

IMG_6088 2024-04-21 23_32_11

With apologies for the handheld video, there's what I'm seeing when I run the test code below. Any suggestions are greatly appreciated, as lvgl_micropython is otherwise working very well for me at this point.

FWIW, if I animate only one of the two sliders, there's rarely (if ever) any issue.

import time, urandom, lcd_bus, rgb_display, lvgl as lv

rgb_bus = lcd_bus.RGBBus(  # ST7262 parallel 800×480 4.3" IPS
    hsync=39,
    vsync=41,
    de=40,
    disp=-1,
    pclk=42,
    data0=8, data1=3, data2=46, data3=9, data4=1,
    data5=5, data6=6, data7=7, data8=15, data9=16, data10=4,
    data11=45, data12=48, data13=47, data14=21, data15=14,
    freq=12500000,
    hsync_front_porch=8,
    hsync_pulse_width=4,
    hsync_back_porch=8,
    hsync_idle_low=False,
    vsync_front_porch=8,
    vsync_pulse_width=4,
    vsync_back_porch=8,
    vsync_idle_low=False,
    de_idle_high=False,
    pclk_idle_high=False,
    disp_active_low=False,
    refresh_on_demand=False,
)

_WIDTH, _HEIGHT = 800, 480
_BUF1 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
_BUF2 = rgb_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

display = rgb_display.RGBDisplay(
    data_bus=rgb_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=_BUF1,
    frame_buffer2=_BUF2,
    backlight_pin=2,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=False,
)
display.set_power(True)
display.set_backlight(100)

scrn = lv.screen_active()
lslider = lv.slider(scrn)
lslider.set_style_size(225, 480, lv.PART.MAIN)
lslider.align(lv.ALIGN.TOP_LEFT, 0, 0)
rslider = lv.slider(scrn)
rslider.set_style_size(225, 480, lv.PART.MAIN)
rslider.align(lv.ALIGN.TOP_RIGHT, 0, 0)

while True:
    lslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    rslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()
kdschlosser commented 2 months ago

I would tinker with the frequency some set it to 13Mhz and also try it at 12Mhz. That's what the problem looks like to me....

I am guessing I fixed the last compile problem yes?

kdschlosser commented 2 months ago

another thing you have going on is this..

while True:
    lslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    rslider.set_value(urandom.randint(0, 100), lv.ANIM.OFF)
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

if you have the refresh rate in LVGL set to 33 milliseconds then you are going all of the crazy changes when things are not getting updated on the display.

Try this instead

slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

while True:
    lslider.set_value(slider_1_value, lv.ANIM.OFF)
    rslider.set_value(slider_2_value, lv.ANIM.OFF)

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    lv.refr_now(lv.display_get_default())

    time.sleep_ms(10)
    lv.tick_inc(10)
    lv.task_handler()
kdschlosser commented 2 months ago

I have been looking at that animation you posted and from what I am seeing is you need to have the frequency set to 12Mhz.

bland328 commented 2 months ago

Yes, the build is great now, thank you very much.

12MHz doesn't work at all--all frequencies I've tried below 12.2MHz throw the screen into some sort of demo mode that slowly cycles forever through full-screen black, white, red, green, and blue.

I settled on 12.5MHz after seeing that this project defines the frequency for this specific board as (12.5*1000000), and hoping they knew something I didn't.

For the record, I haven't changed #define LV_DEF_REFR_PERIOD 33 in lv_conf.h.

Lastly, the code you provided doesn't appear to affect the glitching:

IMG_6093 2024-04-22 15_37_35

kdschlosser commented 2 months ago

do me a favor and render a single horizontal bar across the display. I am curious to see how that looks.

Are you compiling with the board set to ESP32_GENERIC_S3 and the bopard variant set to SPIRAM_OCT???

kdschlosser commented 2 months ago

also try using just a single frame buffer and see how it does.

I have a display here that is 800 x 480 using an ESP32 and I am not getting that behavior. It's gotta be something with the vertical refresh or the horizontal refresh that is causing it.

kdschlosser commented 2 months ago

There is it. You have a setting that's not right.

pclk_active_low in the bus should be set to `True'

So the clock isn't going to work properly and that would result in a flicker.

bland328 commented 2 months ago

There is it. You have a setting that's not right.

Thanks for catching the missing pclk_active_low=True, but sadly, it didn't change anything.

Are you compiling with the board set to ESP32_GENERIC_S3 and the bopard variant set to SPIRAM_OCT???

Yes, I'm building with ESP32_GENERIC_S3 and SPIRAM_OCT.

do me a favor and render a single horizontal bar across the display.

I hope this is what you wanted to see. I let it run for quite a while, and didn't spot any glitches: IMG_6096 2024-04-22 18_03_08

also try using just a single frame buffer and see how it does.

With _BUF2 = None, the effect is similar, but worse; the left slider also glitches, and the glitches are exaggerated: IMG_6094 2024-04-22 17_46_45

kdschlosser commented 2 months ago

OK so take the vertical example and make the sliders smaller in size. Like 50 x 300 and see is the glitching stops.

I am trying to isolate if this is an LVGL problem or a display/driver problem. Another test is to make 2 bars that are 700 x 50.

kdschlosser commented 2 months ago

so let me give you a little bit of a skinny as to what I believe is happening. MicroPython has a large amount of boiler plate code that has to run and that is going to slow things down. I think what we are seeing is because of speed related issue. RGB Displays don't have any GRam to store data. the information is getting written directly to the display. The buffers are in a constant state of being sent to the display using DMA memory. because both buffers are allocated in SPI RAM the large amount of data that is being written by LVGL is causing issues with the data being read from the memory and written to the display.

I believe I might have a solution to this problem.

bland328 commented 2 months ago

OK so take the vertical example and make the sliders smaller in size.

I tried that very thing (though not 50 x 300) just before reading your reply, and the results are interesting--no glitching, except when updating the upper-right corner:

IMG_6098 2024-04-22 18_38_03

Another test is to make 2 bars that are 700 x 50

Did exactly that, and saw no glitches.

I think what we are seeing is because of speed related issue. RGB Displays don't have any GRam...I believe I might have a solution to this problem.

That's great! And I'm happy to test whatever you'd care to send my way, naturally.

EDIT: I just tried the two 50 x 300 sliders you suggested, and see no glitches at all.

But, if I change that to 50 x 480, I get near-constant glitching in the upper right: IMG_6100 2024-04-22 22_23_32

EDIT 2: In case you haven't already seen it, this thread discussing ESP32-S3, PSRAM throughput issues and bounce buffers may be applicable.

kdschlosser commented 2 months ago

OK I changed a few things so hopefully it should work better. Give it a try...

kdschlosser commented 2 months ago

the issue with using bounce buffers is the really large amount of work that needs to be done by the CPU to copy buffers.

I am thinking the tearing you are getting is being caused by LVGL and calling lv_display_flush_ready. That gets called when the buffer has finished being written. When using DMA memory the writing of the buffer is not using the CPU it is being done via a DMA transfer. That means the CPU is able to go along it's merry way. So while one buffer is being emptied the other one can be filled. If the filling takes less time than the emptying that flag doesn't get set to allow LVGL to flush the second buffer. So what happens is the one core on the processor goes into a spinning wheels state because LVGL uses a while loop until the flush ready function gets called. This is where the hangup is. the flush ready gets called from inside Python code, so in order to make that call it has to be done outside of an ISR. so now the ISR has to contend with switching into the main thread that is spinning wheels. It has to try and sneak the function call in there between a loop in the LVGL code. That is where I believe the tearing is coming from.

So what I did is call the python function right after the color gets written to the display so it doesn't happen inside of an ISR context any more. I use a couple of semaphores to control when the data actually gets passed off to be written. the semaphores don't cause a spinning wheel so the core doesn't peg out to 10% usage.

I am hoping that this is what was causing your issue. I feel pretty good about this being the solution. I really do hope it is otherwise I am at a loss for what could be causing the problem.

kdschlosser commented 2 months ago

how are you capturing the output??? are you recording the video using your phone?

If you are and the rig is 100% stationary and not movinbg at al then I am going to really say that is is going to be an hsync and/or vsync setting that is off. The reason why is because look closely at the videos. The entire GUI is moving in what looks like a figure 8 pattern.

kdschlosser commented 2 months ago

Take a look at

these 2 frames. Clock on them in order to blow them up so you can see what I am talking about.

frame_000_delay-0 03s

frame_136_delay-0 03s

Look at the red rectangle I drew and look at how far away it is from the edges In one capture the left side has a large amount of black and i the other it is a whole lot skinnier.

I am almost 100% sure that the frequency is off a little bit. I would adjust it up or down by 100 at a time and watch the display for that shifting. You should be able to dial it in closer. If you cannot do it wit the frequency then you will need to adjust the hsync and vsync settings. Start with one of them and make a wild change to see what it does. so you have an idea if that setting would be able to correct it.

kdschlosser commented 2 months ago

IDK if you have your github set to the dark theme or not. If you do then you 100% have to click on each image to see what I am talking about.

bland328 commented 2 months ago

how are you capturing the output??? ... If you are and the rig is 100% stationary ....

Sorry for the mislead, but It is 100% handheld, so the drifting is me. In person, the image doesn't drift at all. If I shoot any more video, I'll come up with a stationary rig.

OK I changed a few things so hopefully it should work better. Give it a try...

I made a fresh clone, and getting these errors:

/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c: In function 'rgb_bus_trans_done_cb':
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:55:5: error: parameter 'callbacks' is initialized
55 |     esp_lcd_rgb_panel_event_callbacks_t callbacks = { .on_vsync = rgb_bus_trans_done_cb };
|     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:67:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
67 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:226:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
226 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:234:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
234 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:243:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
243 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:252:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
252 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:276:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
276 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:354:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
354 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:440:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
440 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:447:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
447 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:484:5: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
484 |     {
|     ^
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: error: parameter 'mp_lcd_rgb_bus_type' is initialized
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
In file included from /Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/lcd_types.h:7,
from /Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:6:
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:505:19: error: 'mp_lcd_rgb_bus_make_new' undeclared (first use in this function); did you mean 'mp_lcd_rgb_bus_type'?
505 |         make_new, mp_lcd_rgb_bus_make_new,
|                   ^~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:785:44: note: in definition of macro 'MP_DEFINE_CONST_OBJ_TYPE_EXPAND'
785 | #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x
|                                            ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:789:179: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS_2'
789 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, N, ...) MP_DEFINE_CONST_OBJ_TYPE_NARGS_##N
|                                                                                                                                                                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE'
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:505:19: note: each undeclared identifier is reported only once for each function it appears in
505 |         make_new, mp_lcd_rgb_bus_make_new,
|                   ^~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:785:44: note: in definition of macro 'MP_DEFINE_CONST_OBJ_TYPE_EXPAND'
785 | #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x
|                                            ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:789:179: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS_2'
789 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, N, ...) MP_DEFINE_CONST_OBJ_TYPE_NARGS_##N
|                                                                                                                                                                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE'
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:759:243: error: non-static initialization of a flexible array member
759 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } }
|                                                                                                                                                                                                                                                   ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:785:44: note: in definition of macro 'MP_DEFINE_CONST_OBJ_TYPE_EXPAND'
785 | #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x
|                                            ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:789:179: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS_2'
789 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, N, ...) MP_DEFINE_CONST_OBJ_TYPE_NARGS_##N
|                                                                                                                                                                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:798:71: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS'
798 | #define MP_DEFINE_CONST_OBJ_TYPE(...) MP_DEFINE_CONST_OBJ_TYPE_EXPAND(MP_DEFINE_CONST_OBJ_TYPE_NARGS(__VA_ARGS__, _INV, 12, _INV, 11, _INV, 10, _INV, 9, _INV, 8, _INV, 7, _INV, 6, _INV, 5, _INV, 4, _INV, 3, _INV, 2, _INV, 1, _INV, 0)(mp_obj_type_t, __VA_ARGS__))
|                                                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE'
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:759:243: note: (near initialization for 'mp_lcd_rgb_bus_type')
759 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } }
|                                                                                                                                                                                                                                                   ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:785:44: note: in definition of macro 'MP_DEFINE_CONST_OBJ_TYPE_EXPAND'
785 | #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x
|                                            ^
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:789:179: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS_2'
789 | #define MP_DEFINE_CONST_OBJ_TYPE_NARGS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, N, ...) MP_DEFINE_CONST_OBJ_TYPE_NARGS_##N
|                                                                                                                                                                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/obj.h:798:71: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE_NARGS'
798 | #define MP_DEFINE_CONST_OBJ_TYPE(...) MP_DEFINE_CONST_OBJ_TYPE_EXPAND(MP_DEFINE_CONST_OBJ_TYPE_NARGS(__VA_ARGS__, _INV, 12, _INV, 11, _INV, 10, _INV, 9, _INV, 8, _INV, 7, _INV, 6, _INV, 5, _INV, 4, _INV, 3, _INV, 2, _INV, 1, _INV, 0)(mp_obj_type_t, __VA_ARGS__))
|                                                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: note: in expansion of macro 'MP_DEFINE_CONST_OBJ_TYPE'
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:54:17: error: old-style parameter declarations in prototyped function definition
54 |     static bool rgb_bus_trans_done_cb(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data)
|                 ^~~~~~~~~~~~~~~~~~~~~
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:512: error: expected '{' at end of input
/Users/brian/code/esp-idf/lvgl_micropython/ext_mod/lcd_bus/esp32_src/rgb_bus.c:501:5: error: no return statement in function returning non-void [-Werror=return-type]
501 |     MP_DEFINE_CONST_OBJ_TYPE(
|     ^~~~~~~~~~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors
bland328 commented 2 months ago

The build is working, and I have mixed results.

WIth 50x480 sliders, the glitching is now gone(!), but with 225x480 sliders it is still happening: IMG_6103 2024-04-23 12_20_16 IMG_6102 2024-04-23 12_24_00

EDIT: Hoping it might spark something, I'll mention this ESP-IDF fork featuring esp_lcd_rgb_panel.c changes that the creator describes like this:

I have now achieved a “non-streaming” trigger-by-code method. I turned off the VSYNC interrupts and call the lcd_rgb_panel_start_transmission function to trigger each redraw. Using this modified code, in my program I finish updating the FrameBuffer, and then trigger the LCD peripheral to redraw to the display. This eliminated the “tearing” that occurs when using the “stream_mode” with continuous refresh.

My understanding is that some updates of the RGB display are actually delayed in the case that framebuffer changes aren't yet complete, resulting in a subtly-inconsistent redraw frequency, but no glitching.

kdschlosser commented 2 months ago

I believe the issue you are having now is because of processor power and the size of the display that is connected. the ESP32 is not really the best choice when using a display that has a resolution of 800 x 480. While it will work when not needing to make huge rendering changes it is not suited to have to redraw the entire display over and over again.

If you ran this as the loop the tearing might go away.

slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

while True:
    lslider.set_value(slider_1_value, lv.ANIM.OFF)
    lv.refr_now(lv.display_get_default())

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    rslider.set_value(slider_2_value, lv.ANIM.OFF)
    lv.refr_now(lv.display_get_default())

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    time.sleep_ms(10)
    lv.tick_inc(10)
    lv.task_handler()
kdschlosser commented 2 months ago

you can see the delay between the 2 videos you posted. That delay is being caused by the amount of time it is taking LVGL to render. The only difference in code between those 2 videos is the size of the bar. larger widget = long to draw when changes occur.

bland328 commented 2 months ago

Thanks very much for all your work chasing this!

I believe the issue you are having now is because of processor power and the size of the display that is connected.

Makes perfect sense to me. I'd love to see what would happen if screen redraws could be suspended while framebuffers are being updated, but I suppose that might result in full-screen flickering.

If you ran this as the loop the tearing might go away.

I ran it, and it does eliminate the glitching! However, it also introduces some mild full-screen flickering, and results in some occasional stuttering in the movement of the slider knobs.

I grasp why your code eliminates the glitching, and how that could in turn introduce a bit of stuttering in the movement of the knobs, but I don't quite grasp the flickering--might that be because some screen redraws are missed due to the 10ms sleep? I'm not sure I'm picturing the overall flow correctly there.

kdschlosser commented 2 months ago

It's all about the work being done.

I might be able to eliminate the tearing if a 3rd frame buffer is introduced. I would have to play round robin with them swapping the pointers around but it is going to consume even more memory to do that.

Try this as your looping code and see if it works.

slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

while True:
    lslider.set_value(slider_1_value, lv.ANIM.OFF)

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    rslider.set_value(slider_2_value, lv.ANIM.OFF)

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    time.sleep_ms(33)
    lv.tick_inc(33)
    lv.task_handler()
kdschlosser commented 2 months ago

forcing so many updates could be causing some of the issue. Maybe the tearing is coming from the call to task handler. The next bit of code I want you to test is this one...


slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

import time

while True:
    start_time = time.ticks_ms()
    lslider.set_value(slider_1_value, lv.ANIM.OFF)
    lv.refr_now(lv.display_get_default())

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    rslider.set_value(slider_2_value, lv.ANIM.OFF)
    lv.refr_now(lv.display_get_default())

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    stop_time = time.ticks_ms()
    lv.tick_inc(time.tick_diff(stop_time, start_time))
bland328 commented 2 months ago

Try this as your looping code and see if it works.

That code reintroduces the glitching in the right-hand slider, the result looking like the most recent clip above.

kdschlosser commented 2 months ago

how about the second one?

It could be the task_handler that is inducing the flicker for some reason.

That is why I want you to try the second example. The refr_now's should force the redraw.

There is one last test I want you to run if the flicker is gone with the last code block I posted.

Run this if there is no flicker.


slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

import time

while True:
    start_time = time.ticks_ms()
    lslider.set_value(slider_1_value, lv.ANIM.OFF)

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    rslider.set_value(slider_2_value, lv.ANIM.OFF)

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    lv.refr_now(lv.display_get_default())
    stop_time = time.ticks_ms()
    lv.tick_inc(time.tick_diff(stop_time, start_time))
bland328 commented 2 months ago

The next bit of code I want you to test is this one...

These results are interesting. The full-screen flicker is gone, and there's none of the ugly glitching.

The only flaw now is what I would call true "tearing" on the leading/trailing knob edges--I increased the step size to 10% so it is easier to see here:

IMG_6107 2024-04-23 17_22_12

I'll run the other code you sent shortly...

bland328 commented 2 months ago

There is one last test I want you to run if the flicker is gone with the last code block I posted. Run this if there is no flicker.

With the most recent code you sent, there's still no flicker, but the severe glitching in the right-hand slider (like this) returns.

kdschlosser commented 2 months ago

OK so next step.

Give this a try.

slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

import time

update_start_time = time.ticks_ms()
start_time = time.ticks_ms()

while True:
    if time.tick_diff(time.ticks_ms(), update_start_time) >= 10:
        update_start_time = time.ticks_ms()

        lslider.set_value(slider_1_value, lv.ANIM.OFF)
        lv.refr_now(lv.display_get_default())

        slider_1_value += slider_1_inc
        if slider_1_value in (0, 100):
            slider_1_inc = -slider_1_inc

        rslider.set_value(slider_2_value, lv.ANIM.OFF)
        lv.refr_now(lv.display_get_default())

        slider_2_value += slider_2_inc
        if slider_2_value in (0, 100):
            slider_2_inc = -slider_2_inc

    stop_time = time.ticks_ms()
    if time.tick_diff(stop_time, start_time) < 1:
        time.sleep_ms(1)
        continue

    lv.tick_inc(time.tick_diff(stop_time, start_time))
    start_time = time.ticks_ms()
kdschlosser commented 2 months ago

That link you gave for the RGB driver. That was before the bounce buffer ability was added to the ESPIDF. A lot of that code has been added to ESPIDF 5.0. That code is also for using a bounce buffer. The problem that is occuring are the large areas of the screen needing to be rendered in LVGL and then that area has to be copied from one buffer to the other. The organization of when things are done in LVGL is what I think is causing the hangup.

I opened an issue with LVGL to discuss it.

https://github.com/lvgl/lvgl/issues/6118

bland328 commented 2 months ago

OK so next step. Give this a try.

Looks good, except for the occasional tearing on knob leading/trailing areas, just like this previous result (but moving just one pixel at a time, in this case).

...The organization of when things are done in LVGL is what I think is causing the hangup.

Ah, got it. Thanks very much for the explanation!

kdschlosser commented 2 months ago

Now the most ideal thing with the RGB is to not have any sleeps or moments where the program stalls.

When dealing with RGB since there is no GRAM as soon as the buffer finishes emptying it needs to start sending the data again. It doesn't matter if the data is the same data or changed data there needs to be no stall between the 2.

I am going to see if I am able to work around the problem by using the second core to manage passing the frame buffer to be rendered to the RGB driver in the ESP_IDF. I think this will only work if there is a 3rd frame buffer involved tho.

Before I do that I am going to modify a file in LVGL and I will have you put that file into LVGL and build it again. I think that a simple reorganization of when the different tasks are performed would solve the problem.

kdschlosser commented 2 months ago

Give this file a go. It is located in lib/lvgl/src/core You have to remove the .txt from the end of the file name. I would add a .bak to the original file in case it doesn't work for ya.

lv_refr.c.txt

I did compile and it does compile without any errors. IDK if it will work tho.

kdschlosser commented 2 months ago

How the design worked originally is it would do all the rendering to the buffer and then flush the buffer to the display. It would sit there and wait until the buffer was finished being flushed and then it would copy the data from the buffer that was just flushed to the other buffer so the rendering could take place.

The whole point of DMA is to be able to do something while the flushing is taking place, not to sit there doing nothing.

So what I did is I moved the flush callback to being done first then I swap the buffers around and copy the data from the buffer that is being sent to the display to the other buffer. I then render to the second buffer while the first one is still being sent.

I did put a wait before flushing the buffer just in the event it does take longer to send the buffer data then it takes for LVGL to copy and render.

kdschlosser commented 2 months ago

There is still some tweaking I can do to make it run faster so if we see an improvement I will make those changes.

bland328 commented 2 months ago

It doesn't matter if the data is the same data or changed data there needs to be no stall between the 2.

Thanks for the detailed explanation. And if there is a stall, is that when the full-screen flicker occurs?

So what I did is...

Very crafty. I admire your skill and dedication.

There is still some tweaking I can do...

Excellent. I'll test the new file now.

bland328 commented 2 months ago

I'm running into this build error, even after recloning and starting from scratch:

/Users/brian/code/esp-idf/lvgl_micropython/lib/lvgl/src/core/lv_refr.c: In function 'refr_obj':
/Users/brian/code/esp-idf/lvgl_micropython/lib/lvgl/src/core/lv_refr.c:999:27: error: 'lv_draw_image_dsc_t' {aka 'struct _lv_draw_image_dsc_t'} has no member named 'original_area'
999 |             layer_draw_dsc.original_area = obj_draw_size;
|                           ^
kdschlosser commented 2 months ago

did you delete the entire thing and clone it again?

kdschlosser commented 2 months ago

I have to get the guys over at LVGL to make the change I am thinking will improve things. I will spend more time trying to figure it out than asking one of them to do it.

bland328 commented 2 months ago

did you delete the entire thing and clone it again?

Yes. Builds fine until I inject the file you sent. I don't find any other references to original_area in the entire repo, FYI.

I have to get the guys over at LVGL to make the change I am thinking will improve things. I will spend more time trying to figure it out than asking one of them to do it.

If there's anything I can post to bolster your request, I'm happy to.

kdschlosser commented 2 months ago

it's ok. I tested it here locally and it didn't work. I made some more changes and compiled it a few more times and I kept on getting segfaults. I just sent a private message to the head guy over at LVGL. I added the videos you posted to show what was going on. He will know how to get rid of that wait or move it to a different area.

kdschlosser commented 2 months ago

The error you were getting was because the file i modified and sent to you is from a earlier commit to LVGL. The file had been changed after that which is why you are getting the error.

bland328 commented 2 months ago

I just sent a private message to the head guy over at LVGL. I added the videos you posted to show what was going on.

Great--fingers crossed. In the meanwhile, if you come up with anything else for me to test, I'm happy to.

kdschlosser commented 2 months ago

The fix is in the works.

https://github.com/lvgl/lvgl/pull/6120

bland328 commented 2 months ago

The fix is in the works.

Impressively quick! Thanks for the update.

kdschlosser commented 2 months ago

in discussion about what else is able to be changed in order to streamline the process to speed it up. In the mean time you can delete line 386 from lib/lvgl/src/core/lv_refr.c This should help somewhat. It has been tested that removing that does improve things.

bland328 commented 2 months ago

you can delete line 386 ... It has been tested that removing that does improve things.

I made that change, but I'm not sure how to best test against it.

So far, I find that if I keep calling lv.refr_now() after changing each slider value, nothing changed--it works fundamentally well, with just a little tearing on the leading and trailing edges of the knobs.

And if I return to calling lv.task_handler() just once per cycle, the glitching returns and the slider knobs move so fast that most positions are never rendered, so they appear to be bouncing around randomly (that is, instead of taking 23 seconds to travel from 0-100%, they take about 1 second).

What testing approach would be most meaningful after removing line 386 from lv_refr.c?

kdschlosser commented 2 months ago

try this..


slider_1_inc = 1
slider_2_inc = -1
slider_1_value = 0
slider_2_value = 100

import time

while True:
    time.sleep_ms(10)
    start_time = time.ticks_ms()
    lslider.set_value(slider_1_value, lv.ANIM.OFF)

    slider_1_value += slider_1_inc
    if slider_1_value in (0, 100):
        slider_1_inc = -slider_1_inc

    rslider.set_value(slider_2_value, lv.ANIM.OFF)

    slider_2_value += slider_2_inc
    if slider_2_value in (0, 100):
        slider_2_inc = -slider_2_inc

    stop_time = time.ticks_ms()
    print('updating widget:', time.tick_diff(stop_time, start_time))
    start_time = time.ticks_ms()
    lv.refr_now(lv.display_get_default())
    stop_time = time.ticks_ms()
    print('refr time:', time.tick_diff(stop_time, start_time))
    print()
    lv.tick_inc(10)

post the output to me. I am interested in knowing how much time it takes to update the widgets and to refresh the display.

bland328 commented 2 months ago

post the output to me. I am interested in knowing how much time it takes to update the widgets and to refresh the display.

All testing was with line 386 of lv_refr.c commented out.

The code as you provided it glitches severely on the right, much like this previous post.

Here's the output for two trips across the screen, with glitching: widget_updates_with_glitches_01.txt

After running that, I added an additional lv.refr_now() call before setting the value for slider 2, like this:

    lv.refr_now(lv.display_get_default())
    rslider.set_value(slider_2_value, lv.ANIM.OFF)

That additional lv.refr_now() call eliminates the glitches (leaving just the gentle tearing as shown here), and results in this output: widget_updates_without_glitches_02.txt

kdschlosser commented 2 months ago

OK that is what I have thought. it takes 2 times longer to run.

bland328 commented 2 months ago

OK that is what I have thought. it takes 2 times longer to run.

I just want to make sure you're clear that those two tests were both with line 386 of lv_refr.c commented out--the only difference between them is my addition of another lv.refr_now() call to cure the glitching.

Would you also like the timing output of a run with line 386 still in place? Or do you have all the information you wanted?