Documentation · Concepts
How the renderer is chosen
A control library does not get to choose its host's graphics API. The application chose it, at
AppBuilder time, before any control existed. So this one does not choose — it asks what it was
given, and tells you what the answer was.
One question, four answers
One lease, four outcomes, and a readout. There is no configuration step and no initialisation failure.
Three of the four are somebody's default. Vulkan is not.
Metal is what a macOS or iOS application starts on. OpenGL is what Windows, Linux, Android and a browser tab start on. The CPU backend is what is left when the lease returns nothing at all. None of those is a decision you have to make.
Vulkan is the exception: no platform selects it on its own, so a process is on Vulkan because the host asked
for it before the application was built — X11RenderingMode.Vulkan,
Win32RenderingMode.Vulkan or AndroidRenderingMode.Vulkan. That is why every host
that is not already on it sees Vulkan reported as RequiresRestart rather than as missing: it is
a switch away, not a limitation. The browser is the one place it is genuinely absent, because a page is
offered WebGL 2 and nothing else.
On a Mac, Vulkan is the one renderer that needs no restart at all
Apple is the exception to the exception. Avalonia's macOS backend offers three rendering modes —
Software, OpenGl and Metal — and there is no fourth to ask for, so no
restart could ever produce a Vulkan device there. And yet Vulkan runs on a Mac perfectly well, through
MoltenVK, which implements it over Metal.
So this is the one place the control does not take the device it was handed. Set
PreferredBackend to Vulkan on a Mac and it creates a MoltenVK device of its own,
on the same GPU Avalonia leased it, renders the frame with the same Vulkan backend every other platform
uses, and hands the finished image back as the MTLTexture that was underneath it all along.
Nothing is copied and nothing is converted; the compositor never learns that something other than Metal
drew the picture. The catalog reports it as Available — live, on the next frame, with no
restart and no relaunch, which is something no other renderer on any platform can say.
MoltenVK has to be on the machine, and there are two ways to arrange that. Add the
Ava3DControl.MoltenVK package, which contains that library and nothing else and is separate
precisely so nobody carries five megabytes of macOS binary who did not ask for it. Or install it
yourself — brew install molten-vk, the Vulkan SDK, a copy beside your application or in its
Frameworks folder, or AVA3D_MOLTENVK pointing at one — and ship nothing extra.
The control looks for all of them and prefers whichever it finds first, in that order.
When none is there, the row reads Unavailable and
BackendOption.MissingComponents has one entry in it:
var vulkan = info.AvailableBackends.First(b => b.Kind == RenderBackendKind.Vulkan);
foreach (var component in vulkan.MissingComponents)
Console.WriteLine($"{component.Name}: {component.Package} or `{component.Command}` — {component.Url}");
// MoltenVK: Ava3DControl.MoltenVK or `brew install molten-vk` — https://github.com/KhronosGroup/MoltenVK
The name, the package, the command and the link are separate fields rather than a sentence, because a UI
wants to make a button out of one and a link out of another and cannot do either with prose. And the
list is separate from Reason on purpose: almost every unavailable row is a permanent fact
about the platform, and a UI that offered to install Metal on Windows would be worse than one that said
nothing. This is the only row anywhere a reader can act on, so it is the only one that says so — test
whether the list is empty rather than testing for the name, which is the whole point of it being data.
The other thing worth knowing before you choose it: it will never be faster than Metal, because it is Metal with a translation layer in front: on this machine the demo's stress scene costs 0.28 ms a frame through MoltenVK against 0.20 ms through the Metal backend directly, each the median of three runs. Choose it to run Vulkan code paths on a Mac, to check a Vulkan renderer without a second machine, or because your product needs the word on a feature table. Do not choose it for speed; the fastest Mac frame is already shipping and it is Metal.
There is no "graphics initialisation failed"
Every branch above ends in something that draws. That is deliberate, and it is the reason the CPU backend exists at all: a control that renders nothing inside a remote desktop session, or on a VM with no GPU passthrough, is a control you cannot ship in a general-purpose application. The fallback is slower — 55 fps against 120 on the benchmark scene — and it carries only the base-colour map, and it says so in RenderInfo. What it does not do is show a blank rectangle.
Asking for a different one
Ava3DView.PreferredBackend is a request, not an instruction, and what happens when you set it
depends on what you are asking for:
- GPU to CPU, or back, takes effect on the next frame. Both live inside the same lease, so the control simply stops using one and starts using the other.
-
One GPU API to another cannot happen in a running process. Avalonia fixes its graphics
API when the application is built, long before your control is constructed. Nothing a control can do will
change it. The demo handles this by relaunching itself with a different switch — see
IEngineRelauncherin the demo source — which is the honest solution, and the reason BackendAvailability has aRequiresRestartvalue distinct fromUnavailable.
Why "unavailable" always comes with a reason
BackendOption carries a sentence, not just a boolean, because the reasons are genuinely different in kind and a user staring at a greyed-out menu item deserves to know which one applies:
Metal, on Windows |
Does not exist on this platform. Nothing can be done about it, ever. |
OpenGL, on macOS |
Exists and works, but the host was built for Metal. Restart it with AVA3D_GL=1. |
Vulkan, on Linux or Windows |
Exists and works, and no platform starts on it by default. The host has to name it before the
application is built, so this is RequiresRestart on every desktop that is not already
running it.
|
Vulkan, on macOS, with MoltenVK installed |
Available. No host on any Apple platform can hand out a Vulkan device, so the control
creates one itself on the card it was leased. Selectable live, without a restart.
|
Vulkan, on macOS, without MoltenVK |
Nothing on the machine implements Vulkan. The reason says so, and MissingComponents names
MoltenVK with a package, a command and a link, because that is a five-second fix rather than a
platform limit.
|
OpenGL, on iOS |
Present, and renders nothing. This is an upstream defect in Avalonia's iOS GL surface rather than a device limit — iOS hardware runs GL ES perfectly well. Metal is selected there automatically. |
| Anisotropic filtering, in a browser | Reported by PlatformSupport as absent, because calling its entry point under WebAssembly aborts the runtime rather than returning an error. |
The picture is the same either way
Metal, OpenGL and Vulkan run the same arithmetic — Cook-Torrance with GGX, height-correlated Smith visibility and Schlick Fresnel, an analytic sky-and-ground environment, and an ACES tone map. Metal and OpenGL agree to a mean channel difference of 0.002 across a full frame; Vulkan and OpenGL, measured on the same machine and the same scene, agree to 0.0016 on the material chart and 0.0002 on a lit cube. The MSL, the GLSL and the SPIR-V are separate files for the obvious reason, but they are transcriptions of each other, and a screenshot from one is a valid reference for the others.
The closest of all of them is the pair that shares a GPU. Vulkan through MoltenVK and Metal directly, rendering the same 2592×1314 frame on the same Mac, differ on twenty-one pixels out of 3.4 million, by at most 26 parts in 255 on those — which is to say they are the same picture, drawn twice, by two different APIs ending in the same hardware. The twenty-one are edge pixels, where two rasterisers are entitled to disagree about which triangle owns a boundary; the figure is the same to the pixel across three runs of each.
Where Vulkan differs it says so rather than differing quietly. It declines to promise anisotropic filtering or a point size at all — both are device features the host enables when it creates the device, and a control library cannot see whether it did. Textures are still mipmapped and sampled trilinearly, so detail seen at a glancing angle holds still as it does on the other two; it is only a little softer. Those rows read false in RenderInfo.Features, which is pessimistic on most desktop drivers and is the only answer that is never wrong.
The CPU backend is the deliberate exception, and a larger one. It shades per vertex rather than per pixel, samples only the base-colour map, and fills whole pixels rather than anti-aliasing their edges. What it does share with the other three is the answer to what is in front of what: it rasterises against its own depth buffer, so hidden surfaces are hidden for the same reason and in the same order. It is a fallback that keeps its promises small, not a GPU renderer pretending to be one of the others.