Hey! Time flies and temperatures have risen to a mind-numbing 39°. Yes it’s Celsius, and I’m not prepared for it. But here I am, typing on a laptop that feels like a boiling kettle and sounds like a propeller plane. And I have some updates on what’s been going on for Orca this past couple of months. Grab some drink with ice cubes and have a nice read.
I worked on a system for describing the Orca APIs as a JSON file. The goal is to help auto-generate documentation and language bindings. There are a couple options to do that:
Write both the API description and the headers manually. This is of course the easiest option to start with, but making sure the description is in sync with the headers rapidly becomes impractical.
Make the description authoritative, and generate C headers from it. It is relatively easy to do, although now the spec might have to include a lot of C-ism (like macro definitions and preprocessor directives) and trivia (inline documentation, section headings, and so on) not really useful or even transferable to other languages. It also makes working on the C codebase a lot less pleasant, since you can’t edit the headers directly, and need to go through this JSON description to generate them.
Make the C headers authoritative, and generate description files from them. One drawback of this approach is that C is a real pain in the butt to parse reliably, and existing tooling for this is not great (hello libclang). You also can’t put stuff in the description file that can not be expressed in C (e.g. array lengths for pointer parameters, doc strings, etc.) unless you litter the codebase with special “annotations” comments, that you also have to parse along with your C declarations.
As a twist to option 3, you could imagine a system that automatically updates the JSON description by adding stubs for missing APIs, but only checks that existing descriptions have a matching declaration in the C headers. This way you can auto-generate all the basic structure of the JSON file, and amend it manually with docstrings and annotations without making the headers bloated and unreadable, while still making sure the description stays in sync with the headers.
I’ve gone for something even simpler (and a bit more stupid) for now: I have one mode that generates a basic description of everything to a temporary file, and I manually copy over the bits that I want to expose to the real JSON file, where I can amend them. A second mode uses libclang to cross-check the descriptions in the JSON file with the headers, so e.g. if I change the parameters of a function it lets me know it’s gone out of sync and I can fix it.
This isn’t fully automated and perfect, but so far it seems to work ok at cutting through enough of the drudgework while not being too intrusive and letting me evolve the description schema as new needs appear.
I then have a script that ingests that JSON file and produces an API documentation in markdown files, which are then passed to MKDocs to generate a searchable website. The API cross-check and the documentation generator runs on a CI job when we push things to the main branch of our github repo.
There’s still some work to do to generate cross-references between types and function declarations, and of course the JSON file itself needs to be completed with more thorough doc strings, but we already have some preview of the docs here: https://docs.orca-app.dev
Michael Kutowski has been working on Odin bindings, leveraging the JSON API description. And Laytan has contributed a new target to the Odin compiler to support Orca. I’m pretty stoked about this, because not only writing Orca apps in Odin will become possible, but they’ll also be simpler to build than in C.
This is how a minimal Orca application that just opens an empty window would look in Odin:
package main
import oc "core:sys/orca"
main :: proc() {
oc.window_set_title("orca fun")
}
And you woud compile it like this:
odin.exe build main.odin -target:orca_wasm32 -out:module.wasm
orca bundle --name Hello module.wasm
No more calling orca sdk-path
and passing a bunch of
flags to the compiler.
The bindings still need some back and forth, tweaking and completing the JSON description with all the necessary info, but they’re close enough to completion that Michael has started writing some proof of concept app with it, playing with the Orca UI system:
Orca uses WebGPU to implement its vector graphics engine. We depend on Dawn, which is Google’s WebGPU implementation for native platforms. It translates WebGPU to the underlying native APIs, e.g. in our case Metal for macOS and D3D12 on Windows.
Our own graphics API has a concept of “surface”, which is a layer you can draw things on using a specific API (e.g. WebGL, or our own vector graphics API). Surfaces can be overlayed, so you can for example draw a 3D scene using WebGL, and overlay a UI on top of it, using the Orca UI system, which renders itself using WebGPU.
On macOS, this is handled by having a separate Core Animation Layer per surface, and letting the OS composite these onto the main view of the window. This is all good because Metal swapchains are created from Core Animation Layers in the first place.
On Windows, both Dawn and OpenGL need a window handle to create a
swapchain. You would then hope you could use transparent child windows
and let them be alpha composited to the main window, but no luck. Turns
out Windows is pretty bad at dealing with windows, especially
transluscent ones. So you need to make some top level windows to create
your swapchains, make them transparent using the
DwmBlurBehindWindow()
API, and hide them using
DwmSetWindowAttribute()
with the DWMWA_CLOAK
attribute (just hiding the window doesn’t work, as it prevents drawing
to it). Then you can use the Direct Composition API to composite the
contents of these cloaked windows into your main window. Needless to
say, Direct Composition is a bunch of COM object that don’t really work
out of the box in pure C, because Microsoft forgot how to make portable
COM (or most likely doesn’t care). So you basically need to rewrite
their declarations with the appropriate inherited methods and GUIDs.
Now, this worked for OpenGL (and OpenGL ES through ANGLE), but on
D3D12 you also have to specifically configure your DXGI swapchains to be
alpha composited (setting the AlphaMode
field of your
swapchain descriptor to DXGI_ALPHA_MODE_PREMULTIPLIED
). And
although Dawn’s surface creation API lets you configure compositing
behaviour, the D3D12 backend was not honoring this setting, meaning all
surfaces were opaque no matter what.
All this took a little time figure out, but I now have a complete patch for it, which we apply before building Dawn in our CI jobs. I also contributed it back to Dawn, and the devs there were super reactive and helpful. It is in the process of being approved, so it should land sometimes soon in Dawn, at which point I can drop my little patching step.
Orca is currently using wasm3 as its WebAssembly interpreter, and it was a great way to jump-start the project and focus on basic windowing and graphics APIs. But it was always meant to be a temporary building block, to be replaced by our own later on.
Having our own WebAssembly engine is actually a pretty critical part of the project, since it will allow us to explore custom WebAssembly extensions more easily, in particular around virtual memory, dynamic linking and composability. We also want the engine to be tightly integrated with the rest of the runtime, to provide advanced interactive visualization and debugging features.
I previously considered using Bytebox, an interpreter written in Zig by Reuben Dunnington, who is also an Orca contributor. We can actually build the Orca runtime with a Bytebox backend instead of Wasm3. But in the end I decided to not commit to that path, for a couple reasons.
As mentionned above, we might want to bend the engine to our specific use case and make it more aware of its surrounding runtime environment. Bytebox, although being more amenable to this than wasm3, is still designed as a standalone and embeddable, spec-compliant interpreter, available to a wider range of applications than Orca. These competing concerns would likely lead to maintaining our custom fork of Bytebox anyway.
The Wasm engine being such a central piece of the project, I’ll need to hack under its hood pretty extensively, so I’d rather be very familiar with its nuts and bolts and have a detailed understanding of the design choices here. I know no better way than tackling the problem first-hand in this situation.
It also comes down to my own familiarity (or lack thereof) with Zig. I’ve written a few little programs in it, but not enough to have a really informed opinion one way or another. It seems to address some janky aspects of C, and the tooling looks generally better, but I’m not sure the productivity gains would make up for the drag of getting as fluent in it than I am in C.
Zig is also in beta, and from what I understand still frequently breaks compatibility. Don’t get me wrong, I think this is the right choice for a young language, because you don’t want to enshrine early mistakes in the language spec or standard library. But it means we would have to deal with this too.
So, I’m currently working on our WebAssembly interpreter replacement!
One fun idea I played with during Handmade Network’s Visibility Jam
is to have a “lossless” parser that builds a full AST of the module.
This allows displaying the AST with annotated and inter-linked nodes, in
a sort of “disassembly view”, which helps debug malformed modules (or
the parser itself!) and could become part of Orca’s debugging interface.
Here’s a tiny proof-of-concept, showing the actual wasm binary (not the
wat
text format) with some readability aids (e.g. hovering
a type index shows a tooltip with the associated type):
Later on you could be able to click nodes to jump to corresponding definitions, show values in various formats, fold sections, display validation errors attached to faulty nodes, etc.
Valid ASTs are then converted from the wasm stack-based, structured control flow model to a register-based bytecode with relative jumps. This bytecode is interpreted by a simple switch loop for now, although it is intended to be replaced by threaded code (either with computed gotos, or with tailcalls like wasm3), and I want to experiment with a template JIT later on.
I hope you enjoy these updates, and keep an eye out for more! Putting in place the foundations for Orca is a slow burn, especially since I now have to fit it in between contract jobs, but I think all this ground work is critical to the long-term vision. And despite a more constrainted schedule, I think we made some good progress in important directions since January, with the help of a handful contributors: Reuben, Shaw, Ben, Michael, Laytan… many thanks to them!
Seeing people manifest interest for Orca and follow its progress makes me super happy, so many thanks to you, dear reader of this post! You can always find me in the Handmade Netwok Discord and drop a few words in the Orca channel. I’ll try to answer the best I can!
And if you find the project’s goals compelling and want to support it, you can help me by donating to Orca on Github Sponsors.
See you for the next update, and take care!
Sign up to get updates on the Orca project.