Unofficial Nextion/TJC User Forum

Project optimization

Hello guys, I`d like to know what is more performance(not flash memory) efficent:

  1. picture with incorporated text that changes to other picture using .pic variable
  2. transparent text on picture that changes to other text using .txt variable
    image

Also is there a way to let`s say ‘benchmark’ and measure such things(irl or debugger)?

Hi and welcome @Cismus!

First a small preliminary note: you don‘t need an extra text component on top of your button; you can write your text directly into the button component and have a picture background at the same time. In the following I‘ll assume that exact scenario.

What‘s faster?

I‘d guess that the images are more efficient since it is a simple, single layer graphic; basically only transfer pixel data from flash to screen. Text on the other hand involves rendering. And if it‘s text on top of a picture, well, the entire text loading and drawing is additional.

Does it matter? How to measure?

Not sure if what I said above matters (assuming my guess was right); never measured it. But unless you have dozens of buttons with frequent updates on the same page, I‘d say no, doesn‘t matter.
Much more important is that even though images might be faster, they‘re a pain in the a** if you ever need to make any change. I know it because I had an entire UI designed with pictures instead of text. Basically any change to the button (button size, font, font size, color, text) requires you to make a new picture, import it into nextion, replace the old one. Think trice about this before you go for that path.

Now for the measurement itself. In case you haven‘t noticed yet, the debugger/simulator runs so much faster than the actual device that it‘s completely unsuitable for any sort of performance measurements. It also very likely works differently under the hood and thus is not only faster but just not comparable timingwise.

If all you want is the difference between both methods, it is rather simple:

printh 01
for(sys0=0;sys0<100;sys0++)
{
  // Press the button
  click button,0
  // Not sure whether doevents is needed for getting meaningful measurements but I‘d say yes. doevents processes all changes and draws a fresh image to the screen.
  doevents
  // Release the button
  click button,1
  doevents
}
printh 02

Run this code for both versions of your button and check with a scope, arduino, etc. how long it takes between the two serial messages. If in both cases you get consistent readings (important!) then the difference between the cases must be the time spent on the additional processing in one of the variants.

This only works because everything else is the same and thus cancels out in the comparison. That also means that you can only state that „variant a is x seconds faster than variant b“ but not „a is x percent faster“, because you‘re not measuring how long each variant takes, but how long each variant plus all the stuff around takes. In a difference that doesn‘t matter but in a quotient it does ((c+a)-(c+b) = (a-b) but (c+a)/(c+b) != a/b, a,b being the times to draw either button, c being the for-loop, the serial processing, etc.).

What would be required to measure a,b directly? Well, ideally you‘d like to have c=0, a.k.a. nothing else running. But that‘s not possible, so you‘d have to determine c. It gets even worse if c is different in either cases. You could f.ex. just run

printh 01
printh 02

Then add the loop. Then the first doevents, then the second, check how the time is scaling with the number of iterations, etc. You could do all this in one step but doing it slice by slice allows you to check each measurement against the others for sanity (f.ex. to catch things like a second doevent being much faster if nothing‘s changed since the previous one; not saying this is the case but you can see how such things could cause false measurements when only removing the two clicks from the example at the top).
This obviously is a deeeep rabbit hole :grinning_face_with_smiling_eyes:

Edit: while Nextion has timers, too, I‘d not use it to make measurements like these because they run on the same system and nobody knows how they interfere with each other (do they update when a loop is running? When doevents is running? Etc). Hence the serial commands to be able to make a measurement with an independent device.
If it‘s a nextion screen with GPIO pins, one could (and likely should) use those instead of the serial.

Kind regards,
Max

1 Like

Wow, first off I`d never expect such extensive response :smiley:

While thinking about literally thousand other things i completly forgot text on component function /facepalm/

In terms of measuring lag im mostly concerned on visual response and as You mentioned, possibly only nextion employees posses this ‘mystical’ knowlegde about how this system actually runs so I woundln`t necessary trust results from serial.
At this moment Im planning to make simple device similar to used in measuring pixel lag of desktop monitors. It would consist of servo(e.g. pressing repeatedly a button), most precise light sensor ill find in lab, mcu with interrupt and some 3d printed chassis. If it works ill make thread with results :wink:

As for the visual lag there’re two things: 1. processing/preparation time and 2. drawing time.
Especially for the lower end, high resolution displays (f.ex. basic series with 800x480 px) the draw time for the entire screen is in the 100s of milliseconds order of magnitude - they just can’t write the data faster to the screen. That means that even 50x100 sized buttons have visible tearing when being redrawn (ofc depends on what you redraw; black on white will obviously be the most noticeable).
In other cases it may be less extreme but it’s still something to keep in mind.

I actually don’t know if this drawing time would be measured with my proposal (a.k.a. is drawing a blocking or non-blocking operation?).
That aside, I can’t see issues with the suggested way. Sure enough, serial is fast. The thing is, it doesn’t need to be fast, it only needs to be repeatable (because then you can profile it as suggested and factor out).
Another advantage is of course that the serial/gpio method works for any sort of profiling on nextion, not only visual stuff.

As for the Nextion guys knowing more, they seem to become a little less secretive about such things recently (which is great and overdue IMO). Sooo there’s a chance that they’ll make a blog post about this in the future.
Til then I’m waiting for your results!

Kind regards,
Max

1 Like

Kind of off topic but your reply got me wondering. I’m using the Enhanced series so any icons/buttons/pics, etc have to be included in the compiled TFT file unlike the Intelligent series where picture assets can be loaded from the micro SD card. I’ve often been confronted with situations where a firmware update on the external microcontroller could really use a new button/icon/picture, etc that doesn’t exist on the compiled Nextion firmware. This requires either a workaround (like using a less them ideal image already on the LCD) or flashing new firmware to the LCD itself to include this new image asset (harder to access in installed setup).

It would be nice to have a quick and efficient way to include any data for these new image assets in the MCU firmware update and send these to the Nextion to create “on-the-fly” at runtime when needed.

Setting the baud rate to 921600 allows for pretty quick bulk data transfer. If your protocol used 256-color bitmaps (eg. one byte of data per screen pixel), you could complete a serial transfer of a 1/4 screen size image asset in around 200 milliseconds. This image asset could be transfer in the background and temporarily stored in the serial buffer (serial reparse mode, ‘recmod=1’, 1024 bytes) or the Nextion EEPROM (transparent data mode, ‘wept’, 1024 bytes). Looping through the individual pixels would be as simple as using the ‘u[index]’ array to read the serial buffer or ‘repo’ command to read from the EEPROM (read in 4-byte/pixel segments).

‘com_stop’ and ‘com_start’ would probably allow for transparently loading the image data into the serial buffer.

With the image data able to be quickly sent over serial in a practical amount of time, the largest remaining performance hurtle would be the actual drawing routine on the Nextion. It should be simple enough to use the ‘click’ operation to call a simple “draw” routine residing in a hidden hotspot.
Nextion doesn’t have a way to directly access the video memory or even a simple pixel on/off operation so drawing would be limited to the ‘line’, ‘draw’, ‘cir’, ‘fill’, etc which isn’t ideal but still workable.

Just for clarity, none of these graphics commands would be sent over serial - only the raw pixel data (think bitmap). Sending a bunch of ‘line xxx,yyy…0xFF0xFF0xFF’ commands over serial would obviously not be fast enough for anything “real-time”. The graphic commands themselves would be running solely on the Nextion with no ‘doevents’ or screen refresh ‘ref’ operations taking place during the rendering operation to allow for the fastest drawing routine.

Has anyone actually tried such a setup or tested the viability and performance of transferring and/or rendering graphics in real-time on the Enhanced or Basic series?

I‘d say that Nextion is just way too slow for such a thing. In theory, if you were running native code, then it could be possible. But since you‘re running on top of the Nextion Interpreter you‘re hit by a severe performance loss (executing a single variable assignment takes >3000 clock cycles or 78us f.ex.). This is btw the reason why I have such a strong dislike for the default argument against new features: „it can [already] be done“ (often said by Patrick to tell people to implement things thenselves in the Nextion language instead them being added to the Nextion firmware). My reply to that is yes, it can be done, but there are >3000 reasons why it shouldn‘t be done.

Aaaanyways, sorry for the tangent. I do indeed have some code that renders a graphic to the screen. It does however involve quite some math per pixel so it is not exactly representative for your case. I limited the resolution to 8x8 pixels per rendered pixel to keep the rendering to a reasonable time (and because that resolution was sufficient for my case).

Code should be easy enough to understand without great explanations

Kind regards,
Max

I did some test on rendering image assets in real-time on the Nextion 3.2 Enhanced.

Here is a list of execution times for rendering a 100x50 image pixel-by-pixel using the various graphic instructions available:
(approximate speed in milliseconds)
‘fill’ 1500ms
‘line’ 1600ms
‘draw’ 1800ms
‘cir’ 2000ms

It makes sense that ‘fill’ would outperform ‘line’ because doesn’t need to perform slope calculations.
The fact that ‘fill’ will execute quicker than ‘draw’ is counter intuitive because ‘fill’ has to draw AND fill a rectangular area while ‘draw’ only renders the perimeter and skips the fill process.

Using the ‘fill’ command to render a larger 200x100 graphic pixel-by-pixel takes a ridiculous 6.25 seconds.

At first glance, rendering images/buttons/etc in real time is not a viable solution considering the Nextion’s limited graphic instructions and lack of low level memory access. But with some optimization and a hardware change this might actually be workable.

The 3.2" Enhanced MCU runs at 48MHz.
The slightly larger 3.5" Enhanced has a 108MHz MCU.
The additional clock speed of the 3.5" reduces pixel-by-pixel rendering time on a 100x50 image from 1.5 seconds to a potentially workable 650 milliseconds.

For my application, most of the image assets I’d be sending to the Nextion would be GUI elements like buttons, sliders, etc. These types of graphic components are typically designed to have a clean, uncluttered appearance which results in long strings of adjacent pixels sharing the same color. This makes these components ideal candidates for optimization.

To test if optimization improved things I exported a 125x50 button image from an existing Nextion project then sent the individual bytes of the bitmap over serial to an optimized real-time rendering routine on the Nextion. The optimized drawing routine simply looks for repeating pixels and uses the ‘fill’ command to render multiple pixels whenever possible instead of drawing every pixel individually.

Speed Comparison:
1875ms : 3.2" Enhanced, 125x50, single pixel render
800ms : 3.2" Enhanced, 125x50, optimized
350ms : 3.5" Enhanced, 125x50, optimized

A 350 milliseconds draw time might be acceptable if it allows adding new buttons/components to existing Nextion projects and pages without requiring any firmware changes to the screen itself.

350ms sounds indeed usable. However, since it shall become a button, you’d need to implement the event code and press/release detection on the MCU side, too.

I guess I myself would still prefer to just do a TFT update. It may not be the fastest in the short run but certainly much cleaner. The proposed „quick“ fix here quickly becomes a serious pile of not-at-all optimal code that costs a lot of ressources on both sides. There may be edge cases but I‘d definitely advocate for a clean update whenever possible.

Kind regards,
Max

@ratnin Quick addition: AFAIK the STM32 micro that Nextion runs on has hardware accelerated rectangle drawing. That might explain the speed difference.

Kind regards,
Max


This forum is in no way affiliated with NEXTION®, ITEAD STUDIO®, TJC®, or anyone else really. All product names, logos, and brands are property of their respective owners. All company, product, and service names used in this website are for identification purposes only. Use of these names, logos, and brands does not imply endorsement from the respective rights holder(s).