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

How the renderer is selected at runtime The control leases the platform graphics API once. A Metal context selects the Metal backend on macOS and iOS, or the Vulkan backend if you asked for one and MoltenVK is installed; an OpenGL context selects the GL backend on Windows, Linux, Android and the browser; a Vulkan context selects the Vulkan backend, which no platform starts on unless the host asked; no context at all selects the Skia CPU backend. RenderInfo then reports which one was chosen and why the others were not. TryLeasePlatformGraphicsApi() asked once, on the render thread a Metal context a GL context a Vulkan context nothing at all Metal macOS, iOS full PBR, all five maps OpenGL / WebGL 2 Windows, Linux, Android, the browser · full PBR Vulkan Windows, Linux, Android, macOS · full PBR Skia, on the CPU software windows, no GPU base colour only RenderInfo — what actually happened ActiveBackend · the one you got, this frame AvailableBackends · every option, each with a plain-language reason PreferredBackend · what you asked for, if you asked

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:

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.