23 comments

[ 3.0 ms ] story [ 37.7 ms ] thread
For prior related work, check out patent US-6377261-B1 "Compiling glyphs into instructions for imaging for execution on a general purpose computer" filed 1996. The basic idea is each character turns into a sequence of "Load Immediate" / "Shift" / "OR to Memory" instructions; so there's no more fetching from a stored bitmap of the font, as the pixels are implicit in the Immediate data in the generated machine code. Then you can get fancy when one row of pixels is the same as the previous row in the character, so you don't even need to do the Load Immediate. Etc. This scheme was used in FrameMaker for Macintosh back in the day, as implemented by Gus Fernandez (who went hog wild on additional optimizations).
Permalink: https://patents.google.com/patent/US6377261B1/en

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.

Since he's not caching the JITted code anyway, might as well!
Something like this was called "sprite compilation" and used a lot in the DOS game coding scene back in the day. The idea was, given a bitmap image of a sprite, produce the code that would draw that sprite on the screen in assembly and incorporate it into your game. Not very flexible, but fast and useful in the days when games had a lot of 2D bits and bobs moving around, especially for those who could not yet afford a 486 and were stuck on an old Tandy 1000 or something...
Thanks! I had a feeling there was something out there, but couldn't easily find it. I can see this is well optimized for speed, but the RAM usage looks scary from the perspective of a really memory constrained chip.

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.

Indeed, it's not clear that my stuff would have any utility in today's higher-end CPUs, either. But it was fun and zippy in the Motorola 680x0 days (I miss writing to the frame buffer). Certainly this is just related to your stuff, but with different goals never mind wildly different time/space trade-off. (Hi, Raph!)
I've been building BASIC32, a retro BASIC interpreter for microcontrollers so I can flash it once and easily create lots of new projects.

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.

It foregoes reading, I suspect (but obviously don't know for certain), for the simple reason that (if I remember right) you usually don't ever want to "read" a framebuffer, because reading the framebuffer is asking the graphics device to give you the current value of one or more pixels, and that is way slower than it being rendered to some output device.
Added "race the beam" to my personal dictionary today :-). It's such a breath of fresh air to read something which is not written by an LLM.
This is cool, I did something similar with a CoreText replacement I've been building recently.

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).

[flagged]
How oddly humorous for an LLM to praise honesty re: LLM contributions while not expressing the same honesty itself.
I think you're implying this comment is LLM-generated. What is the tell? (The em dash? "honest"? "cost"?)
The account itself is new and advertising a service/website with automated comments. That's a very common bot pattern on HN. And it had made 3 (now 4) comments all littered with LLM cliches. All posted 8 hours from the last, except the first two within 10 seconds of each other, all about the same length.

Em dashes are one, "genuinely" is a small red flag. "The XYZ, written out" in another comment. While I am quite confident (and seemingly the HN bot detector agrees with me, every other comment this account has made is now dead), the rest of the tells I see are more difficult to enumerate. Something in the repetitive "The part that clicked for me" that appears in nearly all of them. Silly/obvious observations. Small, strained micrometaphors. "I really like that this feels more like <the thing that it is> than <the thing that it isn't>."

This is a really great project. So many retro computers are OP that they can use large frame buffers and programming to them doesn't feel like being close to the metal back like back in the day which was more intimate.

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 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.

The "racing the beam" technique extended the life of many RAM-strapped systems back in the day, like the Atari VCS/2600 (which ran a 6507 with 128 bytes of RAM!) It's amazing what you can do with it on machines which basically have no RAM and a little software to control the electron beam.
Funny, I was just thinking about JIT'ted blitters the other day. I'm pretty sure the Windows 3.1 BitBlt function was a JIT, probably building a blitter routine from templates, as they had to handle different src/dest ops, different bits-per-pixel and also linear/planar frame buffers.

Symbolasm looks neat.

Related: for a project I needed readable vector fonts in as few bytes as possible. Had to handle full Unicode and any font face.

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!

This is a interesting side project by Ralph Levien, who has famously spearheaded research into a GUI/rendering paradigm for Rust. A breakthrough is still to come, or will modern languages like Goose, which overcome typical Rust limitations, circumvent the need?