Announcing Theo

John Nunley · August 4, 2023

Today I’m happy to officially announce a project that has been the culmination of about five months of work. This makes it probably the most involved programming project I’ve ever done, and I’m proud of the final result.

The GUI ecosystem in Rust is still in a nascent stage. There are well-defined ecosystem crates for doing certain things, like window management. On the other hand, other essential GUI components are still wanting for a good solution. In my own travels, I’ve found that there’s no good out-of-the-box solution to vector graphics yet, which is an essential part of any GUI. The closest thing is piet-common, but it’s hard to integrate into non-druid systems like winit.

theo is my attempt at resolving this issue. It’s an implementation of piet, a common vector graphics API, that can be used with any windowing system that implements the raw-window-handle traits. The goal is to provide a system that can be effortlessly plugged into whatever system you already have in place, and provide a simple, easy-to-use API for drawing vector graphics.

It has a number of benefits over piet-common and other contemporary solutions. First of all, as mentioned, it works with the raw-window-handle interoperability traits, meaning that it should, in theory, work anywhere. Another primary benefit is its use of GPU acceleration. Where possible, it uses either wgpu or OpenGL as the underlying drawing backend, which should provide benefits for systems with lower CPU power, like Risc-V boxes. However, it is able to fall back to a software rasterization backend based on tiny-skia and softbuffer, which should work on any system that can run winit.

theo also uses very few system libraries. piet-common brings in cairo on Linux. On the other hand, aside from GPU libraries like Vulkan and windowing libraries like X11, theo depends on very little.

If you’re building a GUI system, consider giving it a try! See the examples in the repository for an example of how to use it.

The Vellophant in the Room

At the time of writing, the Linebender team is working on a similar vector graphics system named vello. It uses GPU acceleration via wgpu and is designed to work with systems like winit as well. So why did I go to all the effort? Why not just wait for vello to be ready?

First of all, theo and vello use different strategies. vello is a compute-based renderer, while theo uses a rasterize renderer. The difference comes down to strategy: vello tries to use the GPU to the fullest extent by uploading vector data to the GPU and then rendering it there. On the other hand, theo uses a more traditional strategy: converting shapes on the CPU to textured triangles and then pushing those to the GPU. It is this author’s opinion that the rasterization-based strategy is more ergonomic and easier to cache, so I created theo around this strategy. Your mileage may vary.

There are some other minor differences, but overall once vello is actually released it should work similarly to theo. It’s a matter of API preference, really.

Was it hard to make?

Yes.

Other Crates

While building theo, I had to write a handful of other crates to make the current Rust rendering ecosystem easier to work with. Here’s a quick rundown of them:

Twitter, Facebook