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?
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:
line-straddler
, which generates underlines and strikethroughs for glyphs.piet-cosmic-text
, which provides a uniform text rendering interface forpiet
usingcosmic-text
.piet-tiny-skia
, which implements apiet
backend usingtiny-skia
.piet-hardware
, which convertspiet
rendering calls to buffering textured triangles.piet-glow
, which implements apiet
backend using OpenGL.piet-wgpu
, which implements apiet
backend usingwgpu
.