System Memory Pool utilization by WatchUi.Layer instances

Hello everyone!

recently I have started to use WatchUi.Layer instances on recent device models that allocate those layers on the System Memory Pools.

The benefit of using layers for me are:

1) Lower battery consumption
2) Shorter switching time from the watch face to widgets and activities
3) The possibility to use animations

I'm doing mostly analog watch faces and what I found works best for me is an architecture where I'm using 4 Layers + 1 Animation Layer.

I'm initializing the layers with options==null -> Full screen size and system color depth which is quite a lot of memory.

Amazingly this works in the simulator on all devices like Venu2, Fenix7/Tactix7, Epix2, FR965, etc.

On the actual device it works on the Fenix7/Tactix7 and the Epix2 but on the Venu2 and FR965 my watch faces are running out of memory.

Here are a few questions:

- Is there some documentation about the available system memory pool for each model?
- It seems I can't try{}catch the out of memory exception. Can somebody confirm that this exception can't be handled?
- Will setting the color depth of the layer for example to 8bit reduce the used system memory? I would assume it would...
- Why does the API documentation say that the color depth is a hidden feature?
- Why does the simulator not constrain the system memory properly?
- Is there some way to read the system memory utilization in the simulator/on the device?

I appreciate your feedback, Thanks!

Manuel

  • I think you are talking about the Graphics Pool and not system memory.  See https://forums.garmin.com/developer/connect-iq/b/news-announcements/posts/a-whole-new-world-of-graphics-with-connect-iq-4

    The way I understand it is the Graphics pool has a limited size, and if an app needs memory there, things  that aren't Locked: will be freed up to make room,  But there will be the case where there is still no room.  And you'll get the exception.

    Using 4 full screen buffered bitmaps on AMOLED displays is a whole bunch of memory, so running out doesn't surprise me.

    The benefit of using layers for me are:

    Where did you get this list?   It doesn't seem real to me,

  • Thank you for your reply Jim.

    Yes, I'm talking about the Graphics Pool.
    Garmin is using the terms Graphics Pool and System Memory Pool interchangeably.
    https://developer.garmin.com/connect-iq/api-docs/Toybox/Graphics/FontReference.html

    Running out of memory does not suprise me either but since the pool size is a) not documented nor can be examind at run time and b) the out of memory exception can't be gracefully handled by the app extensive use of layers becomes a guessing game.

    The list results by nature and was proofen by measurement.
    Here is an explaination:

    There are certain elements of the watch face that I want to update each second, others just each minute.
    On an analog watch face with a second hand I can divide the elements into a background layer, a data layer, a analog hand layer and a second hand layer.
    If I use just a layout or directly draw into the main drawing context I have to update the whole watch face each second.

    If I use 4 layers instead I can update 2 of the 4 layers and save a lot of computing power and therefore battery life.

    When transitioning from the watch face to the widgets onUpdate is called by the system. Again since I do not need to update all layers the transition is executed much faster.

    In theory in that special case I would not need to to update any layer at all since the watch face is removed from the view anyway which would make for a lightning fast transition. Unfortunately there is no way of differentiating between a regular onUpdate call and a onUpdate call that takes place just before the transition. --> the onHide event is missing for watch face views which is a pitty.

    Additionally I can insert an animation layer anywhere in the list of existing layers.

    With respect to the proof by measurement, here is my latest watch face that uses this technique.
    If you have a Fenix7 or Epix2 (or a MARQ2 I assume) you can see it in action.
    On Venu2 and FR965 this watch face is currently broken.

    https://www.youtube.com/watch?v=kavmjRpICno

    https://apps.garmin.com/en-US/apps/78e40bfa-f897-4dd5-ae9c-1a95ff9fcbf0

    I think this issue requires deep knowledge of the firmware and therefore contact to Garmin developers.
    That is why I'm adding  and  to the conversation.

    Any insight on my questions raised above would be appreciated a lot.

  • Yes, there is no any description and documentation how it runs.

    I have a lot of out of memory errors only devices 4.x and usually on apac. 

    How to manage something without any knowledge?

    Graphic pool should help but it hasn't! Why my apps work well on f6 and it crash (not sure, I can see only era reports) on f7 (no any visible crash on my f7)? I've counted the size of bitmaps/palettes and I'm sure I have enough system memory to run app but system offers me additional memory but crash and I can't resign from pool...

    Often errors are in strange places like var x= x1+2;

    I think errors are in system/other apps and they are propagated to my WF just because it uses buffered bitmap/custom fonts (about 25kB).

  • Small update.

    I have introduced a new setting in my watch face Iconic named Drawing Mode.

    It offers the following options:

    - Conventional (Drawing to the main drawing context without layers)
    - Layered Low Quality (8-Bit color depth set for all layers) - this basically reduces the color depth of an AMOLED device to that of a MIPS device
    - Layered High Quality (options == null -> System color depth)

    I decided to leave the conventional drawing in as well because I have seen that the alpha blending between layers does not always work as well as compared to directly drawing everything into one drawing context.

    I have received feedback from a user that the watch face now runs fine on his FR965 with Drawing Mode set to Layered Low Quality.

    I certainly will leave the Drawing Mode settings in to give my users the chance to test the layered drawing.

    Nevertheless I would love to learn more about the layers to at least get a better understanding about which default setting to choose for which watch model.

  • Having also recently spent some time with layers, I second the notion that layers are great for analog watchfaces on devices with a graphics pool, and also that there are some gaps in the documentation of these concepts.

    In addition to the questions in the original post, I have been wondering why layers do not make use of ResourceReference in a similar way as other objects do when they are loaded into the graphics pool? There's no createLayer() function that returns a LayerReference as far as I can tell; a layer is created with new Layer() whether or not the device has a graphics pool. Does that mean that layers are always "locked in the graphics pool"?

  • WatchUi.Layer internally utilize BufferedBitmap which is allocated from system graphic pool if supported by devices (e.g. all 4.x CIQ products, e.g. f7, epix 2, venu2, fr965 etc.), layer shouldn't consume too much app memory in a reasonable use case.

     Could you provide a sample code that will cause fr965 to run out of app memory but not in epix2, we can take a closer look to make sure there isn't any fw issues in fr965.

    Also want to point out that Layer when added to the view it's much faster to render during onUpdate, as those are directly blit onto the screen buffer rather than going through the VM byte code interpretation. and it's the main reason how AnimationLayer can achieve 30 fps on our watches.

  • Hi Johnny, thank you very much for the explanation.
    I do not think that the watch face runs out of app memory.
    I have a hunge that the app runs out of graphic pool memory. (if this is a thing)
    In the meantime I received stack traces from epix2 users that show the same issue.
    I assume because of the higher screen resolution the memory allocation is more likely to fail on the FR965.

    I'm not sure whether I'm using the layers in a reasonable way. I'm allocating 4 layers with the size of the screen resolution

                mBackgroundLayer = new WatchUi.Layer(null);
                var color_depth = mRequiresBurnInProtection ? {:colorDepth => 8} : null;
                mMiddleLayer = new WatchUi.Layer(color_depth);
                mHandsLayer = new WatchUi.Layer(color_depth);
                mForegroundLayer = new WatchUi.Layer(color_depth);
    and on AMOLED devices I'm applying an 8-bit palette to 3 of the 4 layers to avoid the out of memory exception.
    Unfortunately in rare cases my users still report issue like for example in the following LOG:
    Error: Out Of Memory Error
    Details: 'Failed invoking <symbol>'
    Time: 2023-06-12T16:17:47Z
    Part-Number: 006-B3851-00
    Firmware-Version: '13.14'
    Language-Code: eng
    ConnectIQ-Version: 4.2.1
    Store-Id: 5f1b4546-4166-4840-bd2e-e81811984870
    Store-Version: 15
    Filename: D4R74911
    Appname: FR965 MB
    Stack:
    - pc: 0x3000270c
    - pc: 0x30004606
    - pc: 0x10008311
    - pc: 0x10005fb5
    The code position 0x10008311 is one of the calls to new WatchUi.Layer.
    Are there any guidelines on how to use the graphic memory pool?
    I notice that the graphic pool provides vast amounts of memory but there needs to be a limit somewhere, right?
    Are there any differences between 4.x CIQ devices when it comes to the graphic pool?
    The Simulator never fails to allocate memory from the graphic pool.
  • Hi Manuel, I think 4 full screen size of Layer is bit too much, that'd be almost 1.5MB ram usage, and it falls to unreasonable usage imo.

    our graphic pool is shared by everyone in the system including native apps, so it's possible that the available pool size varies from product to product and time to time.

    Any chance you can reduce your layer size by specifying a width and height, you can offset you layer by passing locX and locY as well.

  • Layers work great for analog watchfaces. They allow me to easily update an indicator (like the heart rate) under the watch hands, for example. But I can relate that one may end up with four layers before long, as I need three for the basic watchface alone (a background layer, one for the hour and minute hands, and one for the second hand).

    Yes, the second hand layer may not need to be a full size layer, maybe one that only covers a quarter of the screen and is moved every 15 seconds will do. That's if you have enough computing time buffer for the additional operations this will require during low power mode.

  • I think 4 full screen size of Layer is bit too much, that'd be almost 1.5MB ram usage, and it falls to unreasonable usage imo

    shouldn't connect iq impose a limit on the amount / percentage of the graphic pool an app can claim. allowing 1 app to claim everything that's available in the graphic pool encourages "unreasonable usage" (you can't blame the dev on this as he's not even aware how much of the pool an app is using)