peterhinch / micropython-font-to-py

A Python 3 utility to convert fonts to Python source capable of being frozen as bytecode
MIT License
385 stars 69 forks source link

Writer class using "Pixel" #1

Closed hoihu closed 5 years ago

hoihu commented 7 years ago

First of all thanks for this utility!

Would you think that calling pixelin the _printchar method would make sense?

The reason I'm asking is that I'm using the 16-bit framebuffer from deshipu (a pending PR in the micropython repo). Since that framebuffer has a different layout than the upstream version I guess it wouldn't work with your tool.

Using pixel may help to make _printcharindependant of the underlying frame buffer.

peterhinch commented 7 years ago

I did consider using pixel and that is undoubtedly the most portable approach in that the Writer class doesn't need to be concerned with mapping. The reason I didn't was purely down to performance: my approach uses byte transfers to update multiple pixels. This reduces memory accesses by a factor of up to 8 depending on alignment. However the actual timings should be quantified so that we can decide which way to go.

I've been engaged in modifying my existing graphics drivers (e-paper and TFT GUI) to use font_to_py.py and see the back of GLCD Font Creator. Finally replacing assorted C to Python translators with one utility to rule them all...

This is done bar further testing and I'll turn my attention to benchmarking the Writer class. I'll report back later this week.

peterhinch commented 7 years ago

OK. Biblical weather persuaded me to do this today. As I expected rendering text is slow. I rendered 95 characters in a 17w*25h font using my bytewise code, then using pixel(). Timings were 389ms and 772ms i.e. 4ms/ch and 8ms/ch respectively. This is on a Pyboard using code posted here. This code is not fully tested, but it does verify that the correct quantity of data is produced and that the last 16 bytes are correct. I think timings are valid - and I was impressed that the slowdown was only 2:1.

Whether these timings are acceptable is moot. For applications like a control-oriented GUI you are likely to render small amounts of text to disparate locations on screen which will be relatively quick. Heavier text output is another matter. Bit blitting in C is probably the only way significantly to improve performance.

Comments welcome!

hoihu commented 7 years ago

Interesting numbers. Thanks.

Have you tried other uPy optimisation as well (viper, native)? Especially the 2 for loops and the pixelmethod of the underlying driver may profit of that.

But since you've done your part on the FFT optimisations using all those tricks I guess that came to your mind already :)

For my usage, a 2:1 slowdown is acceptable - but that's just me using an OLED display for displaying mainly static content like temperature etc.

Thanks for the test code snippet. I can use this and make that work on my display until things have settled (on the upstream framebuffer side).

peterhinch commented 7 years ago

The best candidate for optimisation is the pixel method as it is called very frequently. In my e-paper driver I have two pixel setting methods, one native optimised which clips out-of-bounds pixels. A viper optimised method does no such checking with that responsibility passing to the caller. This was used for text rendering. Pixel by pixel rendering was used because the GLCD Font Creator produced a C file with vertical mapping (the device is horizontally mapped). With my new method of font creation I can produce fonts with horizontal mapping. This has enabled me to use bytewise rendering as discussed above.

So there's scope for optimisation in drivers for specific hardware.

I'm more interested in the extent to which the framebuffer can be made generic so that we don't have to reinvent these wheels with every new device. Byte twiddling is a pain ;-) I have doubts as to whether the framebuffer class can be both generic and offer fast text rendering. I live in hope that folk with sharper brains than mine will find a way...

hoihu commented 7 years ago

I'm more interested in the extent to which the framebuffer can be made generic so that we don't have to reinvent these wheels with every new device

Yeah that should be the goal, make the framebuffer mapping configurable even. But it's a complex subject.

Btw your pixel example worked fine on my display and I now look at a 23 pixel Freesans font in green :)

What came to mind was that the writer instance is a the moment not capable of defining a text color or inverse it (black on white background). Perhaps writer.set_color(r,g,b)would be an option?

peterhinch commented 7 years ago

I think such an extension would be device specific. If you look at the TFT GUI you'll see an example. If a driver for a colour display uses a one bit per pixel framebuffer the implication is that there is some way to tell the device "from now on, use colour (0, 255, 0)". In that instance there may be other device specific arguments such as transparency. I'm not sure that an attempt to generalise it would offer much: the writer of the driver is going to have to deal with the hardware specific aspects in any event.