How to postpone rendering (or have a callback from refresh timer when there is no update)

I’m currently trying to improve the performance/experience when using the RGB peripheral on the ESP32-S3 with LVGL (also see this related issue on the ESP-IDF repository).

By configuring the ESP-IDF RGB display driver to relax_on_idle and making some small tweaks to the ESP-IDF for now, I’m able to make sure that LVGL decides when a frame is “clocked out” to the RGB panel.
This means that in the lvgl_flush_cb I simply copy the data to the buffer used by the RGB peripheral.
If lv_disp_flush_is_last returns false, I immediately call the lv_disp_flush_ready callback.
If it returns true, I tell the RGB peripheral to start transmitting the frame and call lv_disp_flush_ready from the callback/interrupt I get from the RGB peripheral when the transmission is done.

I do this to prevent “tearing”, which can be caused by writing to the same buffer as is currently being “clocked out” to the panel.
This works rather well for screens that have a continuous animation going on.
Unfortunately for screens that do not have an animation going on, the RGB panel starts blinking because the time in between frames is too much.

I can solve this by restarting the transmission of the frame myself when I get the callback/interrupt from the peripheral, but then I have to prevent LVGL from calling lvgl_flush_cb. I’m not aware of a function to “pause” rendering for a while, other than not calling lv_disp_flush_ready after the flush callback is called.

What MCU/Processor/Board and compiler are you using?

ESP32-S3 with RGB interface

What LVGL version are you using?

v8.1.0

What do you want to achieve?

Postpone the rendering while a frame is being transmitted, or have LVGL call lvgl_flush_cb more often, even if there are no updates.

What have you tried so far?

I tried simply “caching” the arguments to lvgl_flush_cb, but the pointers I receive as arguments are not valid outside of the callback (at least, when I tried this, I got garbage on my screen).

Another thing I just thought of as a possible solution would be to have a callback from the display refresh timer when there is nothing to update.
I could then configure the LV_DISP_DEF_REFR_PERIOD to be long enough to transmit a single frame.
That way at least I get a frequent callback to act up on.

The next release (v5) of ESP-IDF will have a public API function to trigger the refresh of the RGB panel (esp_lcd_rgb_panel_refresh).
Using this new API, I can put the control for refreshing the frame (and therefor accessing the buffer) to the application/LVGL, as described in my initial post:

This means that in the lvgl_flush_cb I simply copy the data to the buffer used by the RGB peripheral.
If lv_disp_flush_is_last returns false , I immediately call the lv_disp_flush_ready callback.
If it returns true , I tell the RGB peripheral to start transmitting the frame and call lv_disp_flush_ready from the callback/interrupt I get from the RGB peripheral when the transmission is done.

Unfortunately, as also described above, although you do not have to continuously keep writing the frame buffer to the panel, if you leave too much time in between the screen will go blank.
To prevent this from happening, I would like to propose an optional callback from _lv_disp_refr_timer.
Below I’ve included the updated piece of code I’ve been using during my research. Only the else block is new (and of course the driver structure had to be updated to include the refresh_no_change_cb)

    /*If refresh happened ...*/
    if(disp_refr->inv_p != 0) {
        if(disp_refr->driver->full_refresh) {
            draw_buf_flush(disp_refr);
        }

        /*Clean up*/
        lv_memset_00(disp_refr->inv_areas, sizeof(disp_refr->inv_areas));
        lv_memset_00(disp_refr->inv_area_joined, sizeof(disp_refr->inv_area_joined));
        disp_refr->inv_p = 0;

        elaps = lv_tick_elaps(start);
        /*Call monitor cb if present*/
        if(disp_refr->driver->monitor_cb) {
            disp_refr->driver->monitor_cb(disp_refr->driver, elaps, px_num);
        }
    } else {
      // Inform display driver that there is no update
      if (disp_refr->driver->refresh_no_change_cb) {
         disp_refr->driver->refresh_no_change_cb(disp_refr->driver);
      }
    }

An alternative would be to have a (compile-time?) configuration option that would also call monitor_cb when there is no update and call it with px set to 0. To me that would also make sense.

@kisvegabor Any thoughts on this?

Hi,

Sorry, I haven’t seen your post earlier. For these kind of development related topics feel free to tag me in the first post or open an issue on GitHub.

In v9 (under development) I added an update to this part. There instead of having many callbacks in the driver the display can send events too (similarly to the wiidgets):

It’s more lightweight and flexible than the callbacks.
Now LV_DISP_EVENT_RENDER_READY is called instead of monitor_cb but we can add more events, similarly to your suggestion.

What do you think?