This is the first installment of my Wayland compositor dev diary.
How I got here?⌗
Back in late 2016 I started work on a Wayland compositor written in Common Lisp. I got to the point where I had SDL and DRM backends, but I ran into a performance issue that I struggled to deal with and, in early 2017, with starting a new job in software development my motivation to work on the compositor waned. It could run a few wayland apps but couldn’t, for example, run Firefox.
I did revisit the code once or twice but, and I say this as someone who loves Common Lisp, the dynamic typing just didn’t make it easy to return to the codebase and start off where I left off.
In fact, whilst I was developing the Common Lisp compositor I was looking for a job and so also put together some Wayland code in C#.
Fast forward to early 2019. I had been playing around with Go quite a bit and found it really quite enjoyable to code in. The Go code I was writing seemed to be very simple to understand. I decided to take another shot at a Wayland compositor. Now, Go makes it relatively nice to call C code, but it’s not very fast to have C code call back into Go code (on the order of milliseconds!) from what I’ve read. Rendering at 60 FPS gives us 16 milliseconds to draw a frame. That time to call back into Go therefore looks like it could be a show stopper. I came across this Go implementation of the Wayland protocol. Being oriented towards a client-side implementation I had to hack on it to make it work for use in a compositor.
This time I got to the point where I had a DRM backend (with logind support) and a bunch more apps working, including Firefox (an editor and Firefox are my minimum requirements for actually using the thing). I didn’t have an alternative backend like with Common Lisp so it was basically a compile, switch to VT, try loop.
Go was not without its problems, though these are nitpicks and not showstoppers. One can obviously argue that Go is not suited for a compositor because of GC, but the CPU and memory usage of Go was actually decent. Animation was a smooth 60 FPS, and if nothing on screen was animating CPU usage was basically 0.0%, jumping up to 0.7% intermittently. So no, the performance wasn’t an issue.
One annoying thing is epoll in Go. The epoll event definition in Go is such that you can store an integer (so you can store, for example the file descriptor of the event) but you can’t store a pointer to some data. This means you basically need to store a separate map from this file descriptor to your data to dispatch on the correct object. Again, not a show stopper, but makes the main function quite verbose. You might even argue that’s a good thing as it’s more obvious what’s going on.
Another hack was this. If a client lies about the size of its SHM buffer, and the compositor attempts to access that data, the compositor will receive a SIGBUS signal that will crash the compositor if not handled. With Go you can turn the ensuing panic into being recoverable. However, we usually access that data once we’ve jumped into C code (to upload the texture to OpenGL). Go won’t actually recover if that SIGBUS occurs in C code…it has to have occured in Go code. This means we end up having to throw in an extra access of the final byte of SHM buffer in Go code before we drop into C code in order to provoke a SIGBUS at that point. Show stopper it is not, just a bit ugly. Even uglier is what you have to do to not have the unused access optimised away by the compiler.
I won’t mention generics.
If you haven’t guessed it, I’ve started yet another compositor project. I don’t think I have anymore after this one in me, so hopefully this one will be successfuly.
So what programming language are you writing this one in I hear you ask?
I’ve been keeping my eye on zig for some time, not just with writing a Wayland compositor in mind.
Great features applicable (but not restricted to) writing a compositor:
- full control over memory (no GC)
- the error system is fantastic
- the comptime stuff is fantastic
- it’s increadibly simple to call out to C
- it’s very readable
- fast compile times
Some benefits with respect to Go:
- again no GC
- no worries about calling back into Zig
- less magic going on (no / minimal runtime)
- capitalisation does not imply public / private
As an example, I know that with a client in steady state of animation (meaning that it is not requesting the creation of new object IDs, simply reusing existing IDs) the zig compositor will not perform any allocations.
I would be less confident to say the same thing of the Go implementation. Indeed what motivated me to start with zig was trying to get dmabuf support working and ending up with a bug where I wasn’t receiving the last of three file descriptors. There was some code taking memory from a byte pool (in order to avoid allocations) and it was only taking 24 bytes, which wasn’t enough in this case for the three file descriptors. I then ran in to a crash when trying to increase that value and ended up just wondering why I was accepting all of this complexity just to avoid allocation.
Where I’m at?⌗
I spent a few weeks getting properly to grip with zig, starting with epoll. The epoll code was immediately clearer than the Go code because I could just store a pointer to some data I wanted.
This past week I’ve had off from work and have gone from none of the Wayland protocol implemented to being able to draw hello-wayland and weston-terminal.
The code is clean.
Where I can I’m using statically allocated arrays with a “design decision” length. You can certainly argue this isn’t elegant but are you going to have more than 512 windows open? Probably not unless you’re trying to prove me wrong.
Optionals provide some really nice and clear ways of handling certain things. For example, OpenGL texture handles are simply integers, but the optionals make it really clear that a window may or may not have an texture (during intialisation or some other reason).
I think tagged unions can provide some nice type safety by basically generating code for protocols at the same time so that you can have an Object type that can be one of each type of interface.
Come back soon and I may have followed up with some more dev diary.