lvgl / lv_binding_micropython

LVGL binding for MicroPython
MIT License
237 stars 156 forks source link

Simulator available memory is limiting #264

Open kdschlosser opened 1 year ago

kdschlosser commented 1 year ago

The current amount of allocated memory that the simulator has been coded with is a pretty limiting thing. There is only 1MB of memory that it has been set up to use.

There is no way to get past this roadblock because the maximum amount of memory that the simulator has been compiled with is set to a low amount. This is able to be adjusted by compiling the simulator and using the following in the build command

-s INITIAL_MEMORY=X

where X is the amount of memory to use.

Since it is not uncommon to see an MCU with 8mb of memory or higher it would make sense to provide a larger amount.

A better way is to compile the simulator using -s ALLOW_MEMORY_GROWTH=1 in the build command and add a slider at the top of the simulator that the user is able move to set the amount of memory MicroPython should have available. Since MicroPython runs client side there would be no down side of adding this feature as it would only use up more memory on the client.

I did figure out how to change the display size in the simulator and I did that and tried to load a PNG file that has a resolution of 480x320 which is the resolution of my display. That is when i ran into the memory allocation failure because there is not enough. I confirmed this by importing gc and running the mem_free function and the mem_alloc function.

amirgon commented 1 year ago

Are you referring to the unix port or js port?

You can try increasing the heap size in main.c

I think you can also set it from the command line when running micropython, with the "heapsize" parameter:

heapsize=<n>[w][K|M] -- set the heap size for the GC
embeddedt commented 1 year ago

He's referring to the JS port, and I think I hardcoded the heap size there.

I am surprised that 1MB was too small, but I don't see an issue with bumping it to 8MB. My thought process behind keeping it small was mostly to reflect the fact that target devices have limited RAM.

kdschlosser commented 1 year ago

He's referring to the JS port

I am

I am surprised that 1MB was too small, but I don't see an issue with bumping it to 8MB. My thought process behind keeping it small was mostly to reflect the fact that target devices have limited RAM.

I do agree with the memory allocation and keeping it small. I believe it would be best suited to add a slider at the top of the simulator so the user is able to set the memory to match the board they are using. This covers all bases. It's not hard to know that 1MB is not going to be enough. a single PNG file that covers the entire display is going to eat up that 1mb. Use case would be the E-Bike demo.

You also have to understand that the only way for a user to use the online simulator and add an image is for them to add the byte data as a bytes literal or a byte array directly in the code. Not only is there a memory hit for loading the byte data in LVGL there is also another large hit for just running the code because that byte data is contained in the source file instead of being loaded into memory directly to LVGL from a file. So it is using twice the amount of RAM.

The other thing is the simulator uses 32bit color so a png file that has a resolution of 480 x 320 and is a single color is 164 bytes, the same resolution PNG file that has a 2 color gradient is 161,919 bytes. turn either of those into RGBA data and you get 614,400 bytes of memory use.... so at a minimum to load a full screen image you are looking at 161,919 bytes * 2.56, byte data literal in python is \x00 for each byte so 4 characters at one byte per character, Not all characters in the PNG data will need to be represented at hex,. 92 characters of the total 255 range can be represented as a single ascii character. That about 36% and why you see the 2.56 because that is 4 bytes - 36%. so you are looking at 414,512 bytes just to store the PNG in the module and then you are looking at an additional 614,400 bytes to load the png which makes a total of 1,028,912 bytes and there is 1,024,000 of memory available.

There is no mechanism in place to be able to add a local path on the users computer to the simulator. Not one that I could find. Maybe there is one?

would also be nice to have a way to enter the resolution of the display instead of having to change the startup code in the python script from

##### startup script #####

#!/opt/bin/lv_micropython -i

import lvgl as lv
import display_driver

to

##### startup script #####

#!/opt/bin/lv_micropython -i

import lvgl as lv
from display_driver_utils import driver, ORIENT_LANDSCAPE

drv = driver(800, 600, ORIENT_LANDSCAPE)

which does work to change the display size. Not documented so not easily known.

I know about the memory limit issue and I know that MicroPython is going to have to be recompiled for the simulator because I went and changed the 1 * 1024 * 1024 in the script to 4 * 1024 * 1024 for the simulator and that is when i got the error saying that MicroPython would have to be recompiled setting the initial memory allocation higher than what it is set to I do not remember what it said the initial memory allocation is currently set to.

I think the solution that gives control to the user on how much memory to use is the best way to go about it. This way they can match what their board has. The display resolution would be "fluff" a nicety and I am sure that there would be people that use the simulator that would be pretty happy at having that ability (because they do not know it can be done).

I am running a Windows machine with WSL and I am not able to compile a Windows version of the binding to run as a simulator. I am able to compile it using WSL and it does run but it will crash if the WSL window is moved at all and it also crashes at random times. WSL doesn't crash, only the window for MicroPython does..,

I tried cross compiling the binding using Mingw64 and that doesn't work either because of the signals being use to kill threads and structures not existing in the SDL library when compiling for Windows. The cross compilation of the binding when the host is a linux box and the target is Windows is a no go.

embeddedt commented 1 year ago

Once I verify that the v9 simulator works as normal I will bump the heap up to 8 megabytes.

embeddedt commented 1 year ago

After rereading your comment I decided to leave the default at 1MB but enable runtime growth of the WASM heap (so recompilation is not needed) and allow choosing the desired heap size using a query parameter.

https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?memory=104857600

should give you a simulator with a 100MB heap, once GitHub finishes recompiling it. The heap size is also logged in the developer console now.

kdschlosser commented 1 year ago

Oh fantastic. This is big time helpful. I know that not everyone will have a need to bump the heap size up but having it available will be helpful to have.

kdschlosser commented 1 year ago

@embeddedt

not fixed just yet....

I was able to use this code

import lvgl as lv
from display_driver_utils import driver, ORIENT_LANDSCAPE
drv = driver(width=800, height=640, orientation=ORIENT_LANDSCAPE)

to set the size of the display. This no longer works. I either get this error

Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
  File "https://raw.githubusercontent.com/littlevgl/lv_binding_micropython/f960502a3260f795f21bfa90b2e7d7199430cf88/lib/display_driver_utils.py", line 35, in __init__
  File "https://raw.githubusercontent.com/littlevgl/lv_binding_micropython/f960502a3260f795f21bfa90b2e7d7199430cf88/lib/display_driver_utils.py", line 168, in init_gui
RuntimeError: Could not find a suitable display driver!

when I don't get the traceback If I used the following code it reports the size I have set it to but not the actual size that is being displayed.

_disp = lv.disp_get_default()
screen_width = _disp.get_hor_res()
screen_height = _disp.get_ver_res()

print(screen_width, screen_height)
kdschlosser commented 1 year ago

One other thing. I am not able to save a script using the memory parameter.

amirgon commented 1 year ago
import lvgl as lv
from display_driver_utils import driver, ORIENT_LANDSCAPE
drv = driver(width=800, height=640, orientation=ORIENT_LANDSCAPE)

This seems to work correctly on the unix port. But on the js port, the GUI comes up but does not respect the values passed to sdl_window_create. (Example) @embeddedt - any idea?

embeddedt commented 1 year ago

I'll have to check what the differences are between the old and new SDL driver, because the same code worked in 8.3.

kdschlosser commented 1 year ago

There is also something funky going on with the mouse driver as well.

This doesn't display correctly but the mouse functions as it should when the widget knob is clicked on and moved.

https://sim.lvgl.io/v8.3/micropython/ports/javascript/index.html?script_startup=https://raw.githubusercontent.com/lvgl/lvgl/0d9ab4ee0e591aad1970e3c9164fd7c544ecce70/examples/header.py&script=https://raw.githubusercontent.com/lvgl/lvgl/0d9ab4ee0e591aad1970e3c9164fd7c544ecce70/examples/widgets/slider/lv_example_slider_2.py&script_direct=7bb96fd74969cea697400b1293ab1fe0a7e98fdc

This one does not move correctly. Same code to actuate the knob. For some reason when a single click is made it is interpreted as a double click and the second click is not in the same location.

https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_startup=https://raw.githubusercontent.com/lvgl/lvgl/0d9ab4ee0e591aad1970e3c9164fd7c544ecce70/examples/header.py&script=https://raw.githubusercontent.com/lvgl/lvgl/0d9ab4ee0e591aad1970e3c9164fd7c544ecce70/examples/widgets/slider/lv_example_slider_2.py&script_direct=ab4579b52fef96638afdff7cd9b872bd85a4869e

kdschlosser commented 1 year ago

I found a way to change the resolution that works.

import lvgl as lv
import display_driver

disp = lv.disp_get_default()
disp.set_res(800, 480)
kdschlosser commented 1 year ago

OH just to let you know. By changing the display size using the method I previously posted it fixes the problem with the touch at the same time.

So what needs to be done in LVGL is once the SDL window gets created a call to lv_disp_set_res using the resolution the display was created with fixes the sizing issue and also the touch issue.

You can also update the LVGL code so the create_window function in src/dev/sdl/lv_sdl_window.c reads

static void window_create(lv_disp_t * disp)
{
    lv_sdl_window_t * dsc = lv_disp_get_driver_data(disp);
    dsc->zoom = 1;

    int flag = SDL_WINDOW_RESIZABLE;
#if LV_SDL_FULLSCREEN
    flag |= SDL_WINDOW_FULLSCREEN;
#endif

    lv_coord_t hor_res = lv_disp_get_hor_res(disp);
    lv_coord_t ver_res = lv_disp_get_ver_res(disp);

    dsc->window = SDL_CreateWindow("LVGL Simulator",
                                   SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                   hor_res * dsc->zoom, ver_res * dsc->zoom, flag);       /*last param. SDL_WINDOW_BORDERLESS to hide borders*/

    dsc->renderer = SDL_CreateRenderer(dsc->window, -1, SDL_RENDERER_SOFTWARE);
    SDL_SetWindowSize(dsc->window, hor_res * dsc->zoom, ver_res * dsc->zoom);
    texture_resize(disp);
    lv_memset(dsc->fb, 0xff, hor_res * ver_res * sizeof(lv_color_t));
    SDL_SetWindowSize(dsc->window, hor_res * dsc->zoom, ver_res * dsc->zoom);
    texture_resize(disp);
}
kdschlosser commented 1 year ago

The issue in Java may or may not be corrected by this. I have not tried it. It could be that the display technically speaking has not been created yet in Java and that is why using lv_disp_set_res works. This is because lv_set_res triggers an event to set the resolution and that takes place once the script has fully loaded and the java end of the display has been created.

embeddedt commented 1 year ago

For some reason setting the window size a second time works. This is very interesting, and I suspect it may be an Emscripten bug in that case. Opened a PR to make this change in LVGL itself since it shouldn't cause issues on other platforms.