M5Paper: Warning when using multiple canvases
-
The M5EPD interface supports creating multiple canvases of different sizes at different positions and updating them independently. For the most part this works fine, but there is a big caveat, you cannot safely use different TTF fonts in different canvases, your app will randomly crash.
The pre-rendered TTF data is static across all In_eSPI instances, but each holds its own pointer to its current render. If that render is deleted by another canvas, either directly by deleteRender, or indirectly by loading a new font, that first canvas now has a pointer to deleted data, and at some point will crash when asked to display text.
To use multiple canvases safely, you can only use a single TTF font across the entire application. Different canvas can use different sizes of that font, that works fine, and other canvases can use GFXFF fonts, they also work fine.
If you change the TTF font in use at runtime, after calling loadFont you have to call createRender for each size in use, and call setTextSize for each canvas using TTF fonts, to update their current render pointer, before any canvas has a chance to try to draw text.
-
Hello @murraypaul
if I am understanding correctly, when using more than one canvas I cannot pre-load and pre-render a TTF font at a given size once for each canvas and then draw randomly into each canvas.
However if every time I want to draw into a given canvas I re-load and re-render the TTF font again it should work even with multiple TTF fonts, correct?
For instance this code seems to run ok:
#include <M5EPD.h> M5EPD_Canvas canvas1(&M5.EPD); M5EPD_Canvas canvas2(&M5.EPD); int myCount1 = 0; int myCount2 = 0; bool bRun = true; void setup() { M5.begin(); M5.EPD.SetRotation(90); M5.EPD.Clear(true); canvas1.createCanvas(540, 200); canvas2.createCanvas(540, 200); } void loop() { M5.update(); if(M5.BtnL.wasPressed() == true) { bRun = true; } else if(M5.BtnR.wasPressed() == true) { bRun = false; } if(bRun == true) { char data[20]; sprintf(data, "%10d", myCount1++); canvas1.loadFont("/m5stack/GenSenRounded-R.ttf", SD); canvas1.createRender(72, 256); canvas1.setTextSize(72); canvas1.drawString(data, 0, 0); canvas1.pushCanvas(8, 100, UPDATE_MODE_A2); sprintf(data, "%10d", myCount2++); canvas2.loadFont("/m5stack/Roboto-Bold.ttf", SD); canvas2.createRender(120, 256); canvas2.setTextSize(120); canvas2.drawString(data, 0, 0); canvas2.pushCanvas(8, 400, UPDATE_MODE_A2); delay(100); } }
Thanks
Felix -
@felmue You can load a single TTF font and pre-render at different sizes in different canvases, that works fine. The problem only occurs with deleteRender, loadFont or unloadFont.
Yes, re-loading the font each time works, provided the performance is acceptable.
Loading the GenSenRounded font takes around 220 ms for me, while Roboto takes only around 50 ms, so depending on the font this isn't something you would want to be doing lots of times per page on an application that needs to be user-responsive. The timings are consistent, so it isn't something that improves with repeated calls due to caching. (Obviously the actual timings will depend on your SD card.)Some other options:
a) Convert the TTF files to headers and include them in your project. This drops the loadFont times to 13/3 ms (and also drops the createRender times, but they are already pretty fast).
Downside is that GenSensRounded uses a huge amount of memory. The sample project with the two fonts included uses over 4MB of the 6.5MB available program memory.b) If you know in advance the font sizes you want to use, convert the TTF fonts to header-stored GFXFF fonts. These are massively smaller, and there is no cross-canvas interference, so you can just load the correct font once for each canvas and not worry about it after that.
Both options are discussed in this thread: https://community.m5stack.com/topic/2732/m5paper-text/19
In general, if you have a simple UI and know in advance which fonts and sizes you want to use, the GFXFF font approach is better, it is fast, uses little memory and doesn't depend on having files stored on the SD card.
-