I recently came across a developer discussing Avalonia on Discord who was conflating the wide range of platforms Avalonia supports with a significant increase in development effort. I thought it would be interesting to briefly explore how Avalonia works and explain why bringing Avalonia to new platforms isn't nearly as involved as some might imagine.
How does it compare to MAUI?
Before we go too far, it's essential to understand that Avalonia works very differently from frameworks like MAUI (or Xamarin.Forms), which uses a platform's native UI toolkit, providing a common API across all the supported platforms. MAUI is less a UI toolkit; more an abstraction layer over existing toolkits. The task of rendering the UI and handling user interactions isn't something the MAUI development team needs to worry about, as the device's UI toolkit is responsible for this. Instead, the primary problem for the MAUI development team has been creating an abstraction that conceals the wildly different UI toolkits, each with its quirks. Think of MAUI as a wrapper that provides native controls in an easy to use and lowest common denominator API.
Avalonia - Making Pixel-Perfect Apps
Having established how MAUI works, let's examine how Avalonia's architecture and approach differ. It's worth knowing that Avalonia is in good company in its design and isn't altogether unique as it shares similarities with how Google's Flutter UI toolkit works.
The most significant similarity is that Avalonia also utilises its own renderer, backed either by Skia or Direct2D. The renderer is responsible for drawing the entire application, allowing Avalonia to avoid depending on a device's UI toolkit.
To illustrate why this is significant, we should consider embedded and low-powered devices. Avalonia has become particularly popular among manufacturers producing touch-based embedded industrial devices.
We've found that they'll often use Linux, opting not to install a desktop environment, which would waste the limited storage, memory and CPU available on their low-powered devices. They can do this because Avalonia supports both accelerated rendering via Direct Rendering Manager and a software framebuffer. Given it’s using the same kernel API’s used internally by Xorg and Wayland compositors, it essentially replaces Xorg/Wayland, gaining exclusive access to the graphics hardware.
Beyond industrial device manufacturing, this approach brings countless other benefits, such as ensuring that an app looks and behaves identically across all the supported platforms. The ability to customise any part of the UI for any particular platform is maintained and is as simple as altering XAML styles. With Avalonia, creating great-looking, cross-platform apps without needing to use platform-specific UI APIs for the various devices you're supporting is easy. It's the essence of "write once, run everywhere".
The architecture diagram below gives a good overview of how Avalonia works. Note that the majority of functionality in Avalonia resides in a netstandard2.0 project!
Rendering an application's user interface is just one piece of the puzzle. Avalonia also needs to handle elements such as wiring up the clipboard, windowing, cursors, hardware acceleration and input events. These pieces of the puzzle are required to facilitate Avalonia on a new platform.
Regardless of the intricacies required to create a UI toolkit, between 10 and 15 percent of the Avalonia code relates to facilitating the platforms. Most of the code is agnostic to any platform, meaning it's relatively easy for us to bring Avalonia support to new platforms when we want.
At the fundamental level, supporting a new platform with Avalonia requires two things; the ability to draw pixels and receive input events.
To demonstrate this point, let's look at the Avalonia VNC platform support. While not typically considered a platform in its own right, in the context of Avalonia, VNC meets the prerequisites of being able to draw pixels and receive input events. It can thus be considered a platform!
What's especially surprising about the Avalonia VNC platform support is its implementation stands at less than 200 lines of code! It demonstrates just how little code is required to enable a new platform!
Why does Avalonia support VNC in the first place? Well, it's valuable for a few reasons, with my favourite being it enabled very early testing of web support. Before WASM with Blazor were viable options for enabling Avalonia in the browser, it was possible to run an embedded VNC server serving Avalonia content, delivering rudimentary web support.
While Avalonia has been historically focused on the desktop for almost a decade now, the recently released preview of iOS, Android and Web support represents a new milestone for the project. Supporting these platforms was never a technical problem or a matter of developer resources.
The developers contributing to Avalonia have helped create a cross-platform UI framework that industry leaders such as GitHub, Unity and JetBrains have come to trust. Given the project's remarkable success and massive growth in contributors, it presents an excellent opportunity to look to the future and consider how we can improve mobile and web development.
Top comments (4)
Awesome blog!! I've been using Avalonia for my org's new project. Loving it so far. 😁
Qt's Approach in .Net ecosystem! Great
Avalonia is awesome. About "pixel perfectness" is true - except that fonts are rendered very differently on mac and windows.
Your comment resulted in another curious inquiry for me to explore!
While it's correct that Avalonia UI's are pixel-perfect, things are a little more complex regarding text and sub-pixel rendering.
Each platform has a different rasteriser with Windows using DirectWrite, macOS using CoreText and Linux using FreeType2. We use these different rasterisers to ensure that fonts are readable and text "fits in" with the other apps on the users device.
We have considered using FreeType for all platforms to make font-rendering identical across the platforms, but it isn't trivial and can result in text either being too blurry or too sharp.
If you've some time and want to explore this more deeply, I suggest building Skia with FreeType and seeing what identical font rendering looks like accross platforms. It likely won't be straightforward but you'll see why it's not something you'd really want. You should find the docs on SkiaSharp helpful for creating a custom build.