lovyan03 / LovyanGFX

SPI LCD graphics library for ESP32 (ESP-IDF/ArduinoESP32) / ESP8266 (ArduinoESP8266) / SAMD51(Seeed ArduinoSAMD51)
Other
1.03k stars 189 forks source link

Problem with vertical sync #129

Closed don41382 closed 1 year ago

don41382 commented 2 years ago

I am working on a scrolling effect on the ILI9341 screen.

In my example I am moving a blue rectangle from one side of the screen to the other. This creates (IMO) a vertical sync problem. I also tried using a buffer with DMA, which creates a similar result.

vsync-problem

A ILI9341 library for Teensy boards implemented a vsync. Is that what I need to get it working. Maybe you have an idea.

static LGFX_Sprite buffer;

const int lcd_height = 240;
const int lcd_width = 320;
const int box_height_weight = 150;

void setup() {
    Serial.begin(115200);
    Serial.println("Start ...");

    setPanelConfiguration();

    tft.setPanel(&panel);
    tft.init();
    tft.setColorDepth(8);

    buffer.setColorDepth(8);
    auto ptr = buffer.createSprite(lcd_width, lcd_height);
    if (ptr == nullptr) {
      Serial.println("Buffer sprite could not be created");
      return;
    }

    tft.startWrite();
    tft.setAddrWindow(0, 0, lcd_width, lcd_height);
}

void loop() {
  for (int i=0; i<(lcd_width + box_height_weight);i += 8) {
    buffer.setColor(TFT_BLACK);
    buffer.fillScreen();

    buffer.setColor(TFT_BLUE);
    buffer.fillRect(-box_height_weight + i, 50, box_height_weight, box_height_weight);

    buffer.pushSprite(&tft,0,0);
  }
}
tobozo commented 2 years ago

hey @don41382 thanks for your feedback

It looks like the SPI bus isn't running at optimal speed. Can you share the contents of setPanelConfiguration?

also some ideas:

don41382 commented 2 years ago

@tobozo thanks for your fast response!

My ESP32 is running at the default SPI speed (40MHz) for the ILI9341. But also tried 60MHz (beyond this number the display breaks). But the problem still exists. Actually it doesn't really improve the performance that much. Maybe a few FPS.

I am going to try your scroll tips. Thanks for that. But in the later version I would like to change the image on scrolling, e.g. scale the box in the center and decrease again. So I guess, the only way is a complete redraw.

struct LGFX_Config
{
  static constexpr spi_host_device_t spi_host = VSPI_HOST;
  static constexpr int dma_channel = 1;
  static constexpr int spi_miso = 19;
  static constexpr int spi_sclk = 18;
  static constexpr int spi_mosi = 23;
  static constexpr int spi_dlen = 8;
};

static lgfx::LGFX_SPI<LGFX_Config> tft;
static lgfx::Panel_ILI9341 panel;

void setPanelConfiguration() {
    panel.spi_cs      = 5;
    panel.spi_dc      = 26;
    panel.gpio_rst    = 17;

    panel.gpio_bl     = 4;

    panel.pwm_ch_bl   = 7;
    panel.backlight_level = true;

    panel.rotation = 3;
}
don41382 commented 2 years ago

And what do you think about the vsync command 0x45 some people talk about.

If you're using the SPI interface, there doesn't appear to be a way to detect/control VSYNC (according to the datasheet), but you can use command 0x45 to ask it the current scanline being drawn. If you synchronize to this information, you can avoid changing lines that are currently being refreshed. You can also change the refresh rate of the display to better hide/match your updates.

see this link to see the full conversation

lovyan03 commented 2 years ago

@don41382 In the ESP32, the APB clock is 80MHz, so the clocks that can be set for SPI are 80MHz, 40MHz, 26.67MHz, 20MHz, and more... For the ILI9341, the practical limit is probably 40 MHz, and the display is generally corrupted when 80 MHz is set. This means that it will take about 30.72msec to redraw the entire 240x320 screen, and the frame rate is limited to 32.5fps. This is much slower than the ILI9341 refresh rate, so tearing cannot be avoided. I think the only way to get around this is to do a diff update, similar to the library at the URL you provided. To learn how to do differential drawing with LovyanGFX, take a look at CollisionCircles in examples.

Simply performing differential drawing will reduce tearing, but the only way to prevent it completely is to adjust the timing yourself using the command 0x45. This function of synchronizing with VSYNC is not something that can be simply implemented in LGFX, so it is a task for the user program.

don41382 commented 2 years ago

@lovyan03 Thanks for the explanation! If you could achieve 80 MHz, would that be fast enough?

To learn how to do differential drawing with LovyanGFX, take a look at CollisionCircles in examples.

I looked at that example and tried to understand it. Not an easy going task. Does it still help, when a lot is changing?

This function of synchronizing with VSYNC is not something that can be simply implemented in LGFX, so it is a task for the user program.

Do you have an idea on how to do this or a simple example?

@tobozo hardware scrolling really does the trick! You don't see it, because it's a downsized gif, but it's butter smooth with hardware scrolling.

vsync-smooth

I wish I could get this done with full screen rendering.

lovyan03 commented 2 years ago

The easiest way to use 80MHz with SPI is to use the ST7789. It can be used in much the same way as the ILI9341, but I think it will work most of the time at 80MHz SPI. If you can transmit at 80 MHz, you should have less tearing than if you transmit at 40 MHz. However, it will probably not be able to prevent tearing altogether.

…By the way, and this is important, VSYNC goes in order in the longitudinal direction (320). Perhaps you are using it with the screen turned 90 degrees to the side. In that case, the direction of travel of pushSprite and VSYNC will completely intersect, resulting in more noticeable tearing. Try using it with the screen in portrait mode. This will probably reduce tearing.

don41382 commented 2 years ago

Thanks again both for your help!

I am going to keep it simple for now and work with the hardware scrolling. Do you have some kind of donation option?

tobozo commented 2 years ago

please send all ramen :ramen: donations to @lovyan03

lovyan03 commented 2 years ago

@don41382 Thanks to you for your sponsorship…!! By the way, with the new LovyanGFX v1 (currently in the develop branch), you can use setRotation for sprites. This will allow you to keep the LCD setting in portrait mode, but draw off-screen in landscape mode. This feature may come in handy when you wish to do complex drawing other than scrolling.

don41382 commented 2 years ago

You are welcome. Just one more question on the scrolling part:

I only need one line of my sprite to be drawn. Is there an easy way to do that?

Something like this:

sprite.pushSprite(dst_x = 0, dst_y = 0, src_x = 1, srx_y = 1, src_width = 1, src_height = 200)

I already worked with the data pointer option, but because my tft is on 16bpp and the image on 8bpp it doens't work that well.

tobozo commented 2 years ago

something like this ?

   sprite->setColorDepth( 16 );
   uint16_t* buf = sprite->getBuffer();
//   sprite->setColorDepth( 8 );
//   uint18_t* buf = sprite->getBuffer();
   uint32_t offset = src_x + src_y * tft.width();
   tft.pushPixels(buf+offset, tft,.width());
lovyan03 commented 2 years ago

@don41382 An easy way to do this is to use setClipRect on the LCD you are drawing on. The outside of the clipped area will not be drawn. Since the calculation of the drawing range is done first, the execution cost should be low.

  tft.setClipRect(0, 0, 1, 200);
  sprite.pushSprite(&tft, -1, -1);
  tft.clearClipRect();
tylercamp commented 1 year ago

If you're using the SPI interface, there doesn't appear to be a way to detect/control VSYNC (according to the datasheet), but you can use command 0x45 to ask it the current scanline being drawn. If you synchronize to this information, you can avoid changing lines that are currently being refreshed. You can also change the refresh rate of the display to better hide/match your updates.

I'm using a display with Parallel-16 interface, is there a quick way for me to add VSYNC in my local repo for that case? I'm new to all of this, but the data sheet suggests that some sort of VSYNC is available https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf

lovyan03 commented 1 year ago

@tylercamp The develop branch has been updated to add the getScanLine function. However, it has only been tested with an SPI connection and not yet with a parallel 16-bit connection. I will probably verify the operation with a parallel connection within a few days.

lovyan03 commented 1 year ago

@tylercamp I thank you for your sponsorship ! it is greatly appreciated !!

I have confirmed that getScanLine works with ILI9488 with 16bit parallel connection. Perhaps it will work with ILI9341 as well, get it from the develop branch and try the code below.

If functioning properly, the serial monitor should display a series of numbers in the vicinity of 0 to 320.

#define LGFX_USE_V1
#inlucde <LovyanGFX.hpp>

#include " your setup header.h "

LGFX lcd;
void setup(void)
{
  Serial.begin(115200);
  lcd.init();

  uint16_t line[512];

  uint32_t prev = 0;
  for (int i = 0; i < 512; ++i)
  {
    uint32_t tmp;
    do
    {
      tmp = lcd.getScanLine();
    } while (prev == tmp);
    prev = tmp;
    line[i] = tmp;
  }
  for (int i = 0; i < 512; ++i)
  {
    Serial.printf("line: %d\n", line[i]);
  }
}

By the way, if your LCD panel provides a TE (Tearing Effect) pin, you can also monitor the VSYNC signal with a GPIO input.

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] commented 1 year ago

This issue has been automatically closed because it has not had recent activity. Thank you for your contributions.