Introducing Triton: DirectX 11 driver for QEMU | UTM Blog
Skip to content
In the prequel, we introduced Neptune, a Direct3D protocol forwarding layer for VirtIO. Neptune allowed us to serialize Direct3D API calls across the hypervisor boundary and this allowed us to run Wine games on a Linux guest with a Linux host faster than with DXVK directly in the guest. Admittedly, the payoff there was not that exciting but it laid the groundwork for our real goal: modern graphics acceleration for Windows guests. We have now achieved this by building a brand new Windows driver called Triton which along with Neptune brings full DirectX 11 support to QEMU virtual machines.
Crash Bandicoot Trilogy (x64) running on Windows 11 ARM64 virtualized on macOS through QEMU
What is Triton?
You might be wondering: if Neptune can serialize Direct3D API calls and Windows uses Direct3D, then aren’t we already done? If Direct3D works in Wine, it should also work in Windows, right? After all, what is Wine, if not a Windows emulator? The short answer is: you sort of can. The Neptune Mesa drivers build a d3d11.dll and dxgi.dll which fully implements the Direct3D API set and so if you just put those files next to the game’s executable, it should load them instead of Windows’ own drivers and you can get some games to run that way. This is also the approach done by previous attempts which used DXVK→Vulkan→Venus to run Direct3D locally inside an application. There are a few disadvantages to this approach. First and most importantly, you cannot get good performance because the window compositor (DWM) “sees” your frame as an image so it needs to use CPU blitting to copy the GPU image buffer to the correct window location. You might be able to do some tricks for full-screen applications to scanout natively but you will never get a smooth desktop experience. Second, because d3d11.dll and dxgi.dll are core components of Windows, you cannot replace the system files themselves and expect Windows to still work. Even if you do manage to get it to work, you will not be able to play many games with anti-cheats which specifically detect this kind of modification. That is why it is only possible to get the DLL to load on a per-application basis (and compatibility varies). Which brings us to the last point: needing to copy files to every application you want working graphics acceleration is not a user friendly experience. The correct approach is not to implement the DirectX APIs but to implement the DirectX DDIs (Device Driver Interface).
If you want a full play by play of the entire development process, check out this companion post.
DDI
User mode
Application
Direct3D 11 (d3d11.dll)
User-mode driver (DDI)
DXGI (dxgi.dll)
Kernel mode
Kernel-mode driver
Hardware / Virtualization
In Windows, the application communicates with the system Direct3D and DXGI libraries. The d3d11.dll (as well as older versions) do the complicated work of state tracking and send a more sanitized stream of commands to the user-mode driver (UMD) which implements the DDI. The application also talks to dxgi.dll to initialize the graphics adapters, set up the swapchain, etc. The UMD also goes through the DXGI to talk with the kernel-mode driver (KMD). The KMD is implemented by the graphics vendor (us) to drive the actual hardware (or in our case the virtual hardware). For Wine, we implemented a custom d3d11.dll and dxgi.dll to intercept the API calls and now for Windows, we need to instead implement the UMD and KMD.
So that’s the challenge: implement the UMD with the DirectX DDI interface and also set up a private interface with the KMD which communicates with the VirtIO device.
Lucky for us, the second part has already been solved. Both anonymix007 and arehnman had independently been working on a KMD for Venus (Vulkan). Since Vulkan is a completely independent graphics API, it does not need to implement the DirectX DDI and its UMD is similar to the “replace d3d11.dll” approach in that it talks directly with the KMD to drive commands to QEMU. Since Neptune is modelled after Venus, the high level kernel interfaces (for DMA, command buffers, etc) is very similar and the interface between UMD and KMD is exactly the same. Ultimately, we chose to use anonymix007’s branch as the base because their implementation had more features working on the KMD side.
That leaves us with the hard part. We have to implement the DDI for DirectX 11. When you are designing a new system, it is always wise to understand how others who have come before have solved similar problems. Unfortunately, there are not many open source DDI implementations to draw inspiration from. Windows graphics drivers is a very niche subject and most of the experts work in one of the handful of graphics hardware vendors. This is one of the reasons that QEMU has never got far with Windows GPU acceleration.
Fortunately for us, there are two working open source implementation that we can learn from. First, Mesa has...