JIT bitmap text rendering for microcontrollers
linebender.org
linebender.org
Image 6 (with figures 8 through 12) is fun; you can eliminate the Shift if you're willing to have multiple different JITed functions for each character, each one generated knowing what its shift count is, leading to yet more optimization opportunities.
On the RP2350, a load from memory and a move-immediate both take 1 cycle, because there's a separate data bus to fast SRAM. If I did want more speed and didn't mind an expansion in font size, I could store each glyph in its own byte/word and avoid the ubfx instruction. But on this chip, for a reasonable speed/space tradeoff I'm going to optimize for space every time.
What you give up with the authors approach is "read". The screen becomes write-only. There are only a few functions that require reads: getting a pixels current color and doing operations like flood fills or paints.
Because I was going for the retro feel of BASIC, I wanted to support these, so I opted for a frame buffer. It isn't as technically cool. On the RP2350, it does require low resolution and low bit rates because they often have RAM limited to KB.
If you step-up to the ESP32-S3 variants with 4MB or 8MB of RAM the frame buffer gets much easier.
This implementation reminds me of two of my early experiences: programming display lists on Atari 8-bit and an HP PCL 5e interpreter (with short raster band buffer). I didn't really get into VCS/2600 128-byte beam racing but did do a fair amount of display-list-interrupt on-the-fly coding for homemade games and demos. I really thought the Atari Antic display list system was terrific--thanks to Jay Miner who also did the Amiga--and didn't see much like it again until OS/2 Presentation Manager or Java FX.
The PCL interpreter probably had the closest shape using a few hundred line cyclic buffer. It 'rendered' into y-sorted objects that needed to be later rendered/rasterized. Each object was cut to be at most 255 pixels (0.85") tall and used reverse max-y ordering since it couldn't be drawn until after the beam passed the wraparound scan line still rendering Y - buffer_height.
Setting Claude on the challenge and giving some pointers (use a set of ANS coders! Be lossy as long as there are no errors when rendered!), I was amazed that a typical font can be rendered in just ~40 bytes per used character.
It saddens me that despite this, plenty of websites ship hundreds of kilobytes of custom fonts!
Symbolasm looks neat.
[0]: https://mitpress.mit.edu/9780262539760/racing-the-beam/
My code can JIT + render to the atlas faster at p100 than CoreText can render without the JIT at p50, and with better quality (measured against an exact cover reference).