Jump to content

Speed up GUI operations


king2

Recommended Posts

I have in my project 5 containers, and 4 labels in each.

I want allow user to select active container, so now for each change I:

- apply default style for all containers and labels (25 x gwinSetStyle)

- apply my own style to active container and its labels (5 x gwinSetStyle)

All this done in one shot. If I will rotate rotary switch very quick, I can see how active selection goes from 1 to 5, and this takes about 1 second, which is too much as for me.

I understand that I can apply default style just to previous active one, but now I think how uGFX fast, taking about 1 second to make 150 changes on screen in 1 second, with average size of 10000 pixels each? This is about 1.5Mpix in a second, but hardware acceleration gives actual quantity much less than 1.5Mpix.

I use STM32F429 at 168MHz, with 800x480 display size via LTDC driver with DMA2D in gdisp_lld_blit_area().

May be, I'm doing something wrong?

How to speedup it?

Link to comment
Share on other sites

If you are manually setting the style on each control this will cause each control and each child control (if the control is a container) to redraw. If you then set the style on the children as well that will cause the children to redraw again.

Thus child controls are being redrawn multiple times, once for each level of parent containers it has to go through.

There are a number of strategies that may reduce this redrawing...

  • If you look at the documentation for gwinSetStyle you will see there is the option to set the global style and whether to ripple it to all controls. Doing this may result in less redraw (and much simpler code) than enumerating each control to set the style.
  • Make your parent container (such as the page container) invisible first, set the style's on everything, then make it visible again. For a page container this would have the effect of clearing the screen (as you make the page invisible) and then redrawing all objects once in the new style (when you make the page visible again).
  • Advanced Technique - Look very carefully at the redraw strategy you have GWIN set to. These are set using defines in your gfxconf.h. By default GWIN uses a combination of synchronous and asynchronous redrawing which provides the best strategy and least RAM and stack usage in normal situations. Synchronous redraws are immediate but use a huge amount of stack. Asynchronous redraws add latency to redrawing but allow collapsing of multiple redraw requests. In your circumstance, a more asynchronous strategy is going to be best as multiple redraw requests will be effectively collapsed in a single redraw attempt on each control.

I also suspect that in your case you are pushing the bandwidth availability of the SRAM bus on your STM32 processor due to the size of your screen. This probably lowers the available bandwidth to the SRAM for CPU access.

Remember for redrawing you currently have 3 or 4 sources of bus utilisation...

  • The video controller reading the ram for display on the LCD. Because of the dual layer nature of the LTDC controller this could be multiplied by 2.
  • The DMA2D graphics accelerator writing data into the RAM
  • The CPU directly writing into the RAM in order to update the display RAM
  • Any other access to the RAM chip for other application purposes eg offline image caching etc

Link to comment
Share on other sites

I can see only two parameters of gwinSetStyle() - handler and style. Can you point me to exact place where I can apply custom style to all children of container?

I have few places where I modifying content, in 3 threads. One thread updates date and time in header, one more thread will update USB flash free space, one more thread - everything else. Best strategy in my case will collect changes, and then call something like redrawAllPending(). More, I have much memory, so I can just redraw all display to another videobuffer, and change address of such buffer at end of LTDC display iteration (after last line was sent to display), so next frame will be displayed from new buffer with new contents.

In y case I think it will be nice to:

- change address of LTDC videobuffer to second one (will display copy of old content)

- make whole page container invisible at old videobuffer, just to not redraw immediately

- make all changes I want (styles, texts and so on), as I understand, nothing will be redrawed in this case

- make page visible, redrawing all objects

- change address of LTDC videobuffer back to first one (will display copy new content)

- use DMA2D or DMA to copy all content from first to second videobuffer

Is it best way?

It will give me determinate time of changing content (in fact, it will be more if I have changed only one text or less if I was changed many styles in one shot).

What will be If I (container and label styles is my_own_style):

1. change container's style to default

2. change label's styles (inside container) to default

3. change container's style to my_own_style

4. change label's styles (inside container) to my_own_style

What will be done in asynchronous mode?

1. it will do from 1 to 4

2. it will do 3 and 4

3. it will do nothing because at the end nothing was changed?

Can I use async mode with manual redrawing content?

Can I redraw only changed things in this mode?

Thank you!

Link to comment
Share on other sites

Change the frame address will not help at all. In fact it will slow the whole operation as it increases the amount of video RAM writing that has to occur - whether by CPU or by DMA2D. Steps 2, 3, 4 from below are fine - ignore the rest. Note that setting the page container to invisible and then visible again will automatically redraw everything. No need to explicitly add redraw calls.

The call you want is gwinSetDefaultStyle(). Documentation can be found at http://api.ugfx.org/group___widget.html#gae7a414480b2c34040746ca897a189b70.

Link to comment
Share on other sites

Ok, but if I will hide all page, it will disappear visually? I will see blinking of all page, isn't it?

I have tried to turn off GWIN_REDRAW_IMMEDIATE, all things goes faster.

In my case it runs quick enough even if I do 150 changes of styles (I did not removed it to get more advanced showcase).

But I still have flickering on screen because it sill draws empty container first, then empty area for text, then text. I can see this on screen.

I have tried to add DMA2D copying at end of _gwinFlushRedraws(), it slow down things, but removes blinking. I use SDRAM, so SDRAM bus used by LTDC, and by other thread to save audio data. I have tried to turn off audio, and this was not helped, so I think it is not bandwidth issue. I will try to use memory-to-memory DMA to copy videobuffer, to leave DMA2D for uGFX needs.

Link to comment
Share on other sites

The redrawing strategy for ugfx is much simpler than a normal desktop operating system. Because it doesn't support clipping shapes (due to the code and RAM requirements to implement such a feature), when it wants to redraw a container it needs to first redraw the container and then redraw each of it's children. Each of the children then appear to flash as the container is drawn on top of them before the child gets redrawn itself.

Unfortunately nothing can be done about this without implementing clipping shapes which we will not be doing anytime soon for the reasons above.

Now that you have changed your redraw strategy, compare setting the page to invisible or not first. It may or may not improve the drawing behaviour you see depending on how many redundant redraws the new strategy has been able to save.

GWIN_REDRAW_IMMEDIATE is a very good flag to have turned off. When this flag is on all redrawing must be done on the current thread at the time the causal event is triggered. As this is normally buried deep in an input driver GWIN_REDRAW_IMMEDIATE causes this all to occur on the now very deep stack thus causing very high stack usage.

By turning this off you have sped up your redraw and also significantly reduced your stack usage. :)

In general, unless there is a specific need to play with the GWIN redraw flags they should be left as the default values ie unspecified (all FALSE).

Link to comment
Share on other sites

Thanks!

I have tried to use DMA2, but forgot about 65535 words limit in one transfer :)

May be there is a place inside uGFX where I can put DMA2D code to transfer data from uGFX videobuffer to real videobuffer?

This magic place should:

- be after entire area was redrawn

- have coordinates of redrawn area

So, for example, uGFX will draw container over all, then labels, buttons, and everything else, then I just copy all container's content into real videobuffer? This will reduce DMA transfer (because we will transfer only areas that has been updated), but will update things in one shot, without any blanking period.

Link to comment
Share on other sites

Pixmaps can be used for that.

Note that they take up a lot of ram and they will, in your case, need to be internal ram as the performance issue for you is the external sram bandwidth. Putting the pixmap in external sram with the video buffer will make your problem worse as you are halving your available cpu bandwidth.

In reality it would even be more than a halving performance penalty because you will lose cpu caching due to the ram lines being more than the cpu cache apart in size.

Link to comment
Share on other sites

I have tried to place DMA2D transfer at the end of _gwinFlushRedraws(), this removes any blanking, but adds delays.

In default case I rotates rotary switch from 1 position to 5, and I can see positions 1,2,3,4 and finally 5 on screen.

But if I use second videobuffer with DMA2D at end of _gwinFlushRedraws(), I can see 1 position on screen, and then position 5 in a part of second, like it delays everything. I think delay can be about 300..500ms.

I have tried to place breakpoint before DMA2D transfer to catch if it waits previous DMA2D transfer to be completed, and it was not catched, so it is not DMA2D too busy. I have added additional transfer to SDRAM, this was not slowed down anything. I have removed audio transfer from SDRAM, speed also was not changed, so external SDRAM bandwidth is not an issue here.

I have removed all lines where I change anything on screen, and indeed, breakpoint in _gwinFlushRedraws() was not fired if I do not touch rotary. So I have placed counter there and found that _gwinFlushRedraws() fires only two times - for position 1 and for position 5.

Then I have returned videobuffer to default address, not touching DMA2D transfer that still copies entire content into another address. I clearly see on screen how selected position was moved from 1 to 2, 3, 4 and 5, and only then was fired _gwinFlushRedraws()'s releaselock for first time!

I cannot understand what is going on:

- which function (and why) painted selected positions 2, 3, 4, 5, before _gwinFlushRedraws() was fired

- why _gwinFlushRedraws() was fired if we have all redrawn

I have GWIN_REDRAW_IMMEDIATE set to FALSE and GWIN_REDRAW_SINGLEOP set to TRUE.

Finally, I have found WM_Redraw() that redraws everything in addition to _gwinFlushRedraws().

I can imagine only one way how it can be - I changing selections too fast, so when it finished to paint one selection, it is another pending redraw ready from next selection, so it refreshes all objects one by one, and finishes at fifth selection (no more changes pending). If I copy buffer at end of _gwinFlushRedraws(), I will get exactly this behavior - normally it will display selections from 1 to 5, but if I will copy buffer at the end of function, it will copy buffer only one time and show me only 5th selection (final version).

Having GWIN_REDRAW_SINGLEOP set to FALSE does not helps, this slows down all things instead, because I have too much objects to redraw (25 object in 1.5-2 seconds, i can see how they redraws one by one).

Pixmaps will not help me, because it will be redrawn same way - draw final version, then update videobuffer, changing selection from 1 to 5, without positions 2, 3, 4 in between.

As I understand, when it redraws container, it redraws container itself first, then marks all children to redraw, so I cannot catch moment where container and all its children was actually redrawn? This is a best moment to copy container with all its content into actual videobuffer.

Link to comment
Share on other sites

BTW, gwinSetDefaultStyle() sets style that will be applied to all widgets with default style, but I want to apply it to some widgets only (labels within one of containers).

Except this, I have placed counter to Redraw call in _gwinFlushRedraws(). If I shift switch to one position it displays 25 changes (redraws) which looks like a true: (1 container + 4 labels) x 5 lines = 25. Four position shifts = 100 redraws.

But if I shift rotary switch too fast, it shows 50 redraws only, half of events was lost. I see this because sometimes at first position I have container with default style instead of custom (while labels at this position have new style).

I think _gwinFlushRedraws() starts at 3->2 shift (when positions 1, 3, 4, 5 = default style, 2 = custom) to apply default style to position 1, then in other thread I apply new positon (positions 2..5 = default style, 1 = custom), this marks position 1 as needed to redraw.

Then _gwinFlushRedraws() finishes to paint position 1, marks it as redrawn, and ooops, we have position 1 marked as valid and redrawn, but with previous style set on display (but new style in container's object).

Is this behavior good? May be it is needed to add some locking to not allow updating of object's parameters if object is redrawing now and to redraw it if we updating parameters?

Link to comment
Share on other sites

Please, imagine that you will rotate volume knob at your car audio, and it will change volume value on display only after you finished to rotate the knob. Would you like such user experience?

I'm trying to understand how WM works.

Which function does actual redrawing, is it only _gwinFlushRedraws() that calls WN_Redraw()?

_gwinFlushRedraws() function can reload itself, but what if IMMEDIATE turned off and SINGLEOP turned on?

It will not call TriggerRedraw(), so in which place it will be called next time to redraw again?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...