Making really tiny WebAssembly graphics demos

I've been studying WebAssembly recently, which has included porting some of my m4vga graphics demos. I started with the Rust and WebAssembly Tutorial, which has you use fancy tools like wasm-pack, wasm-bindgen, webpack, and npm to produce a Rust-powered webpage.

And that's great! But I want to know how things actually work, and those tools put a lot of code between me and the machine.

In this post, I'll show how to create a simple web graphics demo using none of those tools — just hand-written Rust, JavaScript, and HTML. There will be no libraries between our code and the platform. It's the web equivalent of bare metal programming!

The resulting WebAssembly module will be less than 300 bytes. That's about the same size as the previous paragraph.

The Typestate Pattern in Rust

The typestate pattern is an API design pattern that encodes information about an object's run-time state in its compile-time type. In particular, an API using the typestate pattern will have:

  1. Operations on an object (such as methods or functions) that are only available when the object is in certain states,

  2. A way of encoding these states at the type level, such that attempts to use the operations in the wrong state fail to compile,

  3. State transition operations (methods or functions) that change the type-level state of objects in addition to, or instead of, changing run-time dynamic state, such that the operations in the previous state are no longer possible.

This is useful because:

  • It moves certain types of errors from run-time to compile-time, giving programmers faster feedback.
  • It interacts nicely with IDEs, which can avoid suggesting operations that are illegal in a certain state.
  • It can eliminate run-time checks, making code faster/smaller.

This pattern is so easy in Rust that it's almost obvious, to the point that you may have already written code that uses it, perhaps without realizing it. Interestingly, it's very difficult to implement in most other programming languages — most of them fail to satisfy items number 2 and/or 3 above.

I haven't seen a detailed examination of the nuances of this pattern, so here's my contribution.

Rewriting m4vgalib in Rust

If this isn't your first time visiting my blog, you may recall that I've spent the past several years building an elaborate microcontroller graphics demo using C++.

Over the past few months, I've been rewriting it — in Rust.

This is an interesting test case for Rust, because we're very much in C/C++'s home court here: the demo runs on the bare metal, without an operating system, and is very sensitive to both CPU timing and memory usage.

The results so far? The Rust implementation is simpler, shorter (in lines of code), faster, and smaller (in bytes of Flash) than my heavily-optimized C++ version — and because it's almost entirely safe code, several types of bugs that I fought regularly, such as race conditions and dangling pointers, are now caught by the compiler.

It's fantastic. Read on for my notes on the process.

Prefer Rust to C/C++ for new code.

This is a position paper that I originally circulated inside the firmware community at X. I've gotten requests for a public link, so I've cleaned it up and posted it here. This is, obviously, my personal opinion. Please read the whole thing before sending me angry emails.

tl;dr: C/C++ have enough design flaws, and the alternative tools are in good enough shape, that I do not recommend using C/C++ for new development except in extenuating circumstances. In situations where you actually need the power of C/C++, use Rust instead. In other situations, you shouldn't have been using C/C++ anyway — use nearly anything else.

Racing the Beam

This post is the fourth in a series looking at the design and implementation of my Glitch demo and the m4vgalib code that powers it.

In part three we took a deep dive into the STM32F407's internal architecture, and looked at how to sustain the high-bandwidth flow that we set up in part two.

Great, so we have pixels streaming from RAM at a predictable rate — but we don't have enough RAM to hold an entire frame's worth of 8-bit pixels! What to do?

Why, we generate the pixels as they're needed, of course! But that's easier said than done: generate them how, and from what?

In this article, I'll take a look at m4vgalib's answer to these questions: the rasterizer.