From eb37c760033a4c1b21817b80d1728baaa191cde7 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Sat, 7 Nov 2026 20:53:59 +1100 Subject: [PATCH 1/2] JIT bitmap text rendering blog post --- .../font-shared-grid-marked-4x.png | Bin 0 -> 3656 bytes .../blog/2026-11-07-jit-bitmap-text/index.md | 282 ++++++++++++++++++ .../blog/2026-11-07-jit-bitmap-text/kern.png | Bin 0 -> 9653 bytes .../2026-11-07-jit-bitmap-text/textdemo.png | Bin 0 -> 91304 bytes 4 files changed, 282 insertions(+) create mode 100644 content/blog/2026-11-07-jit-bitmap-text/font-shared-grid-marked-4x.png create mode 100644 content/blog/2026-11-07-jit-bitmap-text/index.md create mode 100644 content/blog/2026-11-07-jit-bitmap-text/kern.png create mode 100644 content/blog/2026-11-07-jit-bitmap-text/textdemo.png diff --git a/content/blog/2026-11-07-jit-bitmap-text/font-shared-grid-marked-4x.png b/content/blog/2026-11-07-jit-bitmap-text/font-shared-grid-marked-4x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f87f38da4391cb81ed104fb833d057b5f0b310 GIT binary patch literal 3656 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 5 + ldr @r4 = ldr_base + (word_offset << 6) + ubfx_instr += width << 16 + *slot = ldr + // left side bearing + left @r4 = (g as i32)[16..20] + *(code as *u32) = ubfx_instr + // shift: where the glyph starts, relative to the word in r2. Branch is + // also taken if negative. + shift @r4 = pen + left + room @r7 = 32 - width + pen += g >> 26 + #(shift - room) + if #hi goto off_word + // the delay: orr r2, r2, r3, lsl #shift + d @r12 = *(orr_table + shift * 4) + *((code + 6) as *u32) = d + slot = code + 4 + code += 10 + #(n -= 1) + if #ne goto loop + } +``` + +Here's the corresponding machine code, ARM assembly code, and cycle count analysis from the symbolasm analysis tool: + +``` + 134: f859 c028 1 ldr r12, [r9, r8, lsl #2] + 136: f811 8b01 1 ldrb r8, [r1], #1 + 137: f3cc 5705 1 ubfx r7, r12, #20, #6 + 138: f00c 041f 1 and r4, r12, #31 + 139: b387 1/2 cbz r7, .Ljit_text.blank + 141: f855 b024 1 ldr r11, [r5, r4, lsl #2] + 144: ea4f 145c H 1 lsr r4, r12, #5 + 145: eb0a 1484 1 add r4, r10, r4, lsl #6 + 146: eb0b 4b07 1 add r11, r11, r7, lsl #16 + 147: 801c 1 strh r4, [r3] + 149: f34c 4403 1 sbfx r4, r12, #16, #4 + 150: f8c0 b000 H 1 str r11, [r0] + 153: 1934 1 adds r4, r6, r4 + 154: f1c7 0720 1 rsb r7, r7, #32 + 155: eb06 669c 1 add r6, r6, r12, lsr #26 + 156: 42bc 1 cmp r4, r7 + 157: d821 K 0/2 bhi .Ljit_text.off_word + 159: f85e c024 1 ldr r12, [lr, r4, lsl #2] + 160: f8c0 c006 1 str r12, [r0, #6] + 161: 1d03 1 adds r3, r0, #4 + 162: 300a 1 adds r0, r0, #10 + 163: 3a01 1 subs r2, r2, #1 + 164: d1da K 0/2 bne .Ljit_text_0_loop + loop 134-164: 23 cycles per iteration +``` + +The `K` flags indicate a possible delay in branches because the condition isn't known until the cycle before the branch. +Here, `0/2` indicates 0 cycles if the branch is not taken, and 2 if it is. +Neither of these can be optimized. +The `H` flag indicates the instruction is 32 bits rather than 16 because of the use of "hi" registers (r8-r15). +These are available in the LSP as well. + +## Performance + +The basic loop is 23 cycles per glyph for the JIT plus 3 cycles times height for the bitmap generation. +There's an additional cost of about 20 cycles for JIT and 2 cycles times height per 32 bits of output width. + +This is astonishingly fast. +The textdemo in vic-2350 takes about 21k cycles for the JIT and 42k cycles to run the JIT code. +That adds up to about 2.6% of the available throughput of a single core of the RP2350. +Expanding to 565 is actually about 3x more expensive. +So it's fair to say it's as optimized as it can get. + +While this was designed for race-the-beam rendering, it would work just as well writing into a framebuffer, especially a one bit per pixel framebuffer. +Just generate the code, run it for each scanline. + +It's tempting to cache the JIT output, but saving RAM is more important. +The JIT output is about a 10x expansion over the input string. +And, from the numbers above, it's clear that the cost of running the JIT is minimal. + +The name "just in time" is apt; in the implementation in vic-2350, the JIT compiler runs under 100µs before scanout of the video line. + +I wasn't able to find a direct performance comparison to an existing RP2350 text renderer. +Probably the closest is [dvhstx] by Michael Bell and forked by Adafruit. +That also ends up spending approximately 2-2.5 cycles total per pixel, but is limited to a 14x24 grid and also uses an RGB 222 mode, so its output bandwidth is only half as much (and this is where the lion's share of the time goes). + +## Dot Product Kerning + +Kerning is a very important refinement for text rendering, otherwise the spacing looks irregular. +The general mechanism in OpenType fonts is quite complex. +For this project, I wanted something simple and efficient, so dusted off an idea that was originally motivated by trying to do text layout on GPU. + +Basically, each glyph has two bitvectors, one each for the left and right side. +To kern two glyphs, take the `and` of the right bitvector of the first glyph and the left bitvector of the second, then the popcount (number of `1` bits) of that, resulting in the number of pixels to kern. +That's actually just the dot product of the two vectors, bits interpreted as 0 and 1. +The kern tables require only two bytes per glyph, and kerning is super fast to evaluate. + +Even so, kerning evaluation is done at display list build time, not racing the beam. +When the kern is nonzero, a special negative space character is inserted into the string in the text display object. + +I haven't done a real implementation yet, but I suspect this idea might scale to real fonts: instead of bits and an integer kerning amount, the vectors would store continuous values (reasonably quantized). +An additional nonlinear step after the multiplication but before the sum may also help. +Dot products are of course ridiculously efficient to compute on GPU, while doing index chasing through OpenType tables is not. +But that exploration is for another day. + +Kerning improves quality considerably: + +![Comparison of kerning, first line is not kerned, second is](kern.png) + +## AI disclosure + +This work is a refinement of the text rendering in [pico-dvi-rs], which was hand-written. +My draft of the JIT core was hand-written in Rust, then I used Claude to translate it to symbolasm and refine and optimize it. +This blog is hand-written. + +The symbolasm compiler and analysis tool is heavily LLM assisted, though it too evolved from an initial hand-written version. + +I did a [microtypography] experiment that was a pretty cool result, but decided not to include it, partially because it would have increased the fraction of LLM content. + +Like most other people in the industry, I struggle with the balance between quality and the acceleration that LLMs provide. +Claude is exceedingly good at the low level tasks of coding; it easily found an edge case in my draft of the JIT compiler. +I find that when used judiciously, LLM can improve quality, and I strive for that. + +## Related work + +I wasn't able to find much precedent for JIT specifically for text rendering. +Of course, there is a massive literature on JIT in general. +On-the-fly code generation for bitblt was done for the [Blit] around 1982. +See [Bitmap Graphics SIGGRAPH 84] for more. +An interesting detail is that they claim about 2500 characters per second; the raw performance of this on RP2350 hardware is almost 3 orders of magnitude greater, just counting the output to 1bpp (which is how the Blit's framebuffer was configured). + + +[Bitmap Graphics SIGGRAPH 84]: https://pdos.csail.mit.edu/%7Ersc/pike84bitblt.pdf +[Blit]: https://en.wikipedia.org/wiki/Blit_(computer_terminal) +[Xerox Alto]: https://en.wikipedia.org/wiki/Xerox_Alto +[vic-2350]: https://github.com/raphlinus/vic-2350 +[symbolasm]: https://github.com/raphlinus/symbolasm +[pico-dvi-rs]: https://github.com/DusterTheFirst/pico-dvi-rs +[Microtypography]: https://en.wikipedia.org/wiki/Microtypography +[dvhstx]: https://github.com/adafruit/Adafruit-DVI-HSTX diff --git a/content/blog/2026-11-07-jit-bitmap-text/kern.png b/content/blog/2026-11-07-jit-bitmap-text/kern.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7348ff1abcf737ad67c4827cc9405fe6520f9e GIT binary patch literal 9653 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 LU6q$g*1`V=at)G8 literal 0 HcmV?d00001 diff --git a/content/blog/2026-11-07-jit-bitmap-text/textdemo.png b/content/blog/2026-11-07-jit-bitmap-text/textdemo.png new file mode 100644 index 0000000000000000000000000000000000000000..00e6f7b70d76c4cf67a3cd7a5b065113fb3a55a6 GIT binary patch literal 91304 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 b8e1L`}4!8yi(C>Dj36{HqSvn;2H z4B@FZy1OM0WHFRup$gbDKsU>@MVMb>Xa^N-IiZ??1sM-ag-6wIzr4bb3(pi)*VTq0 z9@%)2z@Rq`r?5<#0Wg5=iZGERTRdx2lw_LA0enm?fZ!M#qC!)fEroiGQ6iQEplmVp z!CjtgF%s=5^gGOU1I z4REaJEhoq?15U*ES6yBI4~jwKW@5Lg5tb5%%nqaDs6N)mPDSv4N$?RT$kr)Q1eTC6 z!={xejP*xGh}UEq_S_&{2~Bvcub9VVn?Ja ziD)2I;}jJG_Jw!>cbNth1(UrC)z@wKrW&}}=P<{WGWQRb=H05q4Or|@zaj#p0&+Ed z!cDOo?_OtG$QC-f398A_fM4;njCznftPJ)nQhPcMb;4_mMu}M*A#2Q#Tk6QRUajVo zMvP0M6Jxifj+2lIhA(VM?8&yN(7|OLIv0UtxRoaa4D|J>coAN)otI%p?VOV(aRcD2 zKwyC1v=NzQp#t+uFqBjO23!t9Km?HhjzJAcPa%1VC=c{xkY6m4l(`+trX(bAMr#lR zg!QEz_V5L@XM3!0x4BVFQod@p!H}& z#DNim_yGInw_sKq3R;tBMG3yS6vz``B~Ilyl)z1!6Be?O=WT}p5H=?bH$j+;YXSZQ zcyV*8ONWoekYASIf#WeKm~q9FvBf5NfSX`skoYnl;@y<3l!mHEHWR1)21!F1YfkoJ z27xkdS1}4rb!7u&1Iftif7@6+Ir)p6PIEpXS)IYf{6$&Nk<{OY`e4Od20)pZTHPXK z3FO2hgS=p43!pUo!A&u`L+5CxXanMmRs0N^{yGjL8_BzydJQIM_<&VOBK%%LA~Z8? zvSRUL)bOFoP`<@5 Date: Sun, 8 Nov 2026 09:32:12 +1100 Subject: [PATCH 2/2] Add a little more detail about kerning --- content/blog/2026-11-07-jit-bitmap-text/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/blog/2026-11-07-jit-bitmap-text/index.md b/content/blog/2026-11-07-jit-bitmap-text/index.md index 8487763..df1f63f 100644 --- a/content/blog/2026-11-07-jit-bitmap-text/index.md +++ b/content/blog/2026-11-07-jit-bitmap-text/index.md @@ -240,6 +240,11 @@ The kern tables require only two bytes per glyph, and kerning is super fast to e Even so, kerning evaluation is done at display list build time, not racing the beam. When the kern is nonzero, a special negative space character is inserted into the string in the text display object. +For this font, I built the [kerning table](https://github.com/raphlinus/vic-2350/blob/62f2fae7e672b9a01011cdc320b031cda29fb5c8/fonts/vic-sans-14.kern) manually. +Each bit is a specific feature, for example bit 0 represents the top right of the first glyph projecting out, and a corresponding lack of ink in the top left of the next glyph. +So it is set on the right of `T` and `Y`, and also at the left of most lowercase letters and also `A`. +It's not hard to imagine a tool that might help automate this, perhaps distilling kerning information prepared in other ways. + I haven't done a real implementation yet, but I suspect this idea might scale to real fonts: instead of bits and an integer kerning amount, the vectors would store continuous values (reasonably quantized). An additional nonlinear step after the multiplication but before the sum may also help. Dot products are of course ridiculously efficient to compute on GPU, while doing index chasing through OpenType tables is not.