Triton:QEMU 的 DirectX 11 驱动
摘要
Triton 是 QEMU 的一个新 Windows 驱动,与 Neptune 层配合,通过实现 DirectX DDI(而不是 API 拦截)为 Windows 虚拟机提供完整的 DirectX 11 图形加速。
暂无内容
查看缓存全文
缓存时间:
2026/08/08 17:19
# 介绍 Triton:适用于 QEMU 的 DirectX 11 驱动
来源:https://blog.getutm.app/2026/introducing-triton-directx-11-driver-for-qemu/
在前一篇文章(https://blog.getutm.app/2026/introducing-neptune-direct3d-virtualization-for-qemu/)中,我们介绍了 Neptune,一个用于 VirtIO 的 Direct3D 协议转发层。Neptune 让我们能够将 Direct3D API 调用序列化后跨虚拟机边界传输,从而让 Linux 客户机中的 Wine 游戏在 Linux 主机上运行时,性能优于直接在客户机内使用 DXVK。不可否认,当时的成果还不太令人兴奋,但它为我们真正的目标奠定了基础:为 Windows 客户机提供现代图形加速。现在,我们通过构建一个名为 Triton 的全新 Windows 驱动实现了这一目标,它与 Neptune 一起为 QEMU 虚拟机带来了完整的 DirectX 11 支持。
## 什么是 Triton?
你可能会想:如果 Neptune 能够序列化 Direct3D API 调用,而 Windows 使用的是 Direct3D,那我们不是已经完成了吗?如果 Direct3D 能在 Wine 中工作,那它也应该能在 Windows 中工作,对吧?毕竟,Wine 如果不是一个 Windows 模拟器(https://www.winehq.org/about),那它又是什么呢?简短的回答是:某种意义上确实可以。Neptune 的 Mesa 驱动会构建 `d3d11.dll` 和 `dxgi.dll`,它们完整实现了 Direct3D API 集合,所以如果你只是把这些文件放在游戏可执行文件旁边,游戏就会加载它们而不是 Windows 自带的驱动,这样确实可以让一些游戏运行起来。这也是之前一些尝试(https://github.com/arehnman/kvm-guest-drivers-windows)所采用的方法,即使用 DXVK→Vulkan→Venus 在应用程序内部本地执行 Direct3D。这种方法有几个缺点。首先,也是最重要的一点,你无法获得良好的性能,因为窗口合成器(DWM)把你的帧“看作”一张图像,因此它需要使用 CPU 位块传输(blitting)来将 GPU 图像缓冲复制到正确的窗口位置。对于全屏应用程序,你或许能通过一些技巧实现原生扫出(scanout),但你永远无法获得流畅的桌面体验。其次,由于 `d3d11.dll` 和 `dxgi.dll` 是 Windows 的核心组件,你不能替换系统文件本身并期望 Windows 仍然正常工作。即使你设法让它工作了,很多带有反作弊系统的游戏也无法运行,因为它们会专门检测这种修改。这就是为什么只能以每个应用程序为基础来加载这些 DLL(而且兼容性各不相同)。这引出了最后一点:需要为每个想要获得图形加速的应用程序复制文件,这可不是一种用户友好的体验。正确的做法不是实现 DirectX API,而是实现 DirectX DDI(设备驱动接口)。
## DDI
用户模式
应用程序
Direct3D 11(`d3d11.dll`)
用户模式驱动(DDI)
DXGI(`dxgi.dll`)
内核模式
内核模式驱动
硬件 / 虚拟化
在 Windows 中,应用程序与系统 Direct3D 和 DXGI 库通信。`d3d11.dll`(以及更早的版本)负责复杂的状态跟踪工作,并将更精简的命令流发送给用户模式驱动(UMD),由 UMD 实现 DDI。应用程序还与 `dxgi.dll` 通信,以初始化图形适配器、设置交换链(swapchain)等。UMD 还通过 DXGI 与内核模式驱动(KMD)通信。KMD 由图形硬件厂商(也就是我们)实现,用于驱动实际硬件(或者在我们的场景中,驱动虚拟硬件)。对于 Wine,我们实现了自定义的 `d3d11.dll` 和 `dxgi.dll` 来拦截 API 调用;而现在对于 Windows,我们需要反过来实现 UMD 和 KMD。
这就是挑战所在:实现带有 DirectX DDI 接口的 UMD,同时建立与 KMD 之间的私有接口,让 KMD 与 VirtIO 设备通信。
幸运的是,第二部分已经解决了。anonymix007(https://github.com/anonymix007/kvm-guest-drivers-windows-venus/tree/viogpu3d-venus)和 arehnman(https://github.com/arehnman/kvm-guest-drivers-windows)都独立地开发了一个用于 Venus(Vulkan)的 KMD。由于 Vulkan 是一个完全独立的图形 API,它不需要实现 DirectX DDI,而它的 UMD 类似于“替换 `d3d11.dll`”的方法,直接与 KMD 通信以向 QEMU 发送命令。由于 Neptune 是仿照 Venus 设计的,因此高层内核接口(用于 DMA、命令缓冲等)非常相似,UMD 与 KMD 之间的接口也完全相同。最终,我们选择使用 anonymix007 的分支作为基础,因为他们的实现在 KMD 端有更多可用的功能(https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/38731#note_3389999)。
剩下的就是最难的部分。我们必须为 DirectX 11 实现 DDI。在设计一个新系统时,研究前人如何解决类似问题总是明智的。不幸的是,可供借鉴的开源 DDI 实现并不多。Windows 图形驱动是一个非常小众的领域,大多数专家都集中在少数几家图形硬件厂商中。这也是 QEMU 在 Windows GPU 加速方面一直没有取得重大进展的原因之一。
幸运的是,我们有两个可用的开源实现可以学习。首先,Mesa 有一个 DirectX 10 UMD(https://gitlab.freedesktop.org/mesa/mesa/-/tree/main/src/gallium/frontends/d3d10umd?ref_type=heads)。如果你没有读过上一篇文章(https://blog.getutm.app/2026/introducing-neptune-direct3d-virtualization-for-qemu/#gallium-for-windows),简单来说就是:Mesa 为 Linux 实现了 OpenGL。Mesa 为 OpenGL 做状态跟踪,并发出 Gallium API 调用。Mesa DirectX 10 UMD 是 OpenGL 之外的一种选择,它发出相同的 Gallium API 调用。然后 Gallium 后端驱动(AMD、Intel、VirGL 等)将这些调用转换为原生图形驱动 API。上游 Mesa 仅支持 DirectX 10 的软件光栅化后端,但最近有一些工作让它能够配合 VirGL 运行。不幸的是,macOS 上的 virglrenderer 缺少这个 UMD 所需的许多功能,因此它并不是在 macOS 主机上获得图形加速的可行方案。不过,它与 Mesa 代码库的集成方式为我们集成 Triton 提供了一个清晰的范例。
VirtualBox 拥有唯一可用的开源 DirectX 11 UMD(https://github.com/VirtualBox/virtualbox/tree/main/src/VBox/Additions/win/Graphics/Video/disp/wddm)。然而,这个驱动并不能真正“移植”到我们的场景中使用。它的工作方式是将 DDI 调用翻译成一种中间字节码,然后在主机端,再把字节码解释成 DirectX API 调用。虽然(借助 AI 帮助)把这个字节码发射器和解释器移植到 QEMU 中很容易,但我们出于几个原因决定不这么做。首先,我们认为将 DDI 转换为字节码、再把字节码提升回 DirectX API 这种做法可能引入 bug,从而限制与游戏的兼容性。实际上,从网上的论坛帖子来看,许多游戏正是因为这个问题而无法在 VirtualBox 中运行。如果翻译引擎中缺少某个功能或存在 bug,修复起来需要大量的持续维护工作,我们不希望依赖 Oracle 来做这件事。其次,VirtualBox 的 GPLv3 许可证与 virglrenderer 的 MIT 许可证以及 QEMU 的 LGPLv2 许可证存在不兼容问题。VirtualBox 的代码无法集成进来,但我们确实从中获得了一些宝贵的见解。他们列出了哪些 DDI 原型已实现、哪些返回错误,这份清单被用作实现工作的最低要求。这些信息在 MSDN 文档中并不容易找到,而尝试实现每一个原型将是一个极其庞大的工程。他们的 DXBC 签名算法也很有帮助,因为微软从未在任何地方公开过该算法。
由于我们不想采用 VirtualBox 那种为 DDI 调用使用中间传输格式的方法,我们可以做得更好。如果你把 `d3d11.dll` 想象成一个大致将 DirectX API 调用转换为 UMD DDI 调用的组件,那么我们希望我们的 UMD 做的是把 DDI 调用再转换回 DirectX API 调用。为什么这很有用?因为这样我们就可以使用经过测试且可用的 Neptune 协议,而不必为序列化 DDI 调用而发明新的传输机制。在主机端,我们不需要做任何额外的工作来执行这些调用。VirtualBox 不仅需要在客户机上有发射器和传输层,还需要在主机上有解释器和分发器。每一步都会增加延迟,并引入出错和不兼容的机会。我们仍然需要在客户机上有发射器和传输层,但在主机端我们不需要解释器,因为反序列化后的 Neptune 命令本身就是 DirectX 11 API 调用,可以直接分发,无需任何额外解析。少一个转换步骤,就意味着少一分出错的可能。DDI→API 转换的另一个优势是,D3D11 中的大多数 DDI 调用都有对应的 API 等效调用,这意味着转换就像把一些 API 句柄映射到设备句柄、有时再查一下 API 与 DDI 枚举之间的差异一样简单。然而,最大的收获同时也是整个故事中最复杂的部分,那就是 DXBC 着色器代码。
## DXBC
DXBC(DirectX 字节码)是微软的着色器编译器(FXC)生成的 IR 代码。具体来说,它是较旧的(DirectX 12 之前)格式,由 DirectX 使用的着色器语言 HLSL 编译而来。由于 Triton 充当从 DDI 到 API 的逆向转换层,它不需要反汇编和转换这种着色器字节码。这对我们来说在复杂性和兼容性方面都是一个巨大的胜利。不幸的是,事情并没有简单到可以把字节码原封不动地传给主机。
应用程序
作者编写 HLSL 着色器源码
HLSL 源码
FXC
HLSL → DXBC 着色器编译器
生成
DXContainer
头部 + 各部分
头部
魔数、版本、部分表
SHDR
DXBC 字节码
ISGN
输入签名(元数据)
OSGN
输出签名(元数据)
……其他元数据部分
ID3D11Device::CreateVertexShader
传递整个容器
d3d11.dll
Direct3D 11 运行时
pfnCreateVertexShader
仅传递 SHDR 部分
UMD(Triton)
用户模式驱动
编译器(FXC)会生成 DXBC 字节码以及其他元数据。`d3d11.dll` 期望看到这些元数据并会消费它。当 DDI 被调用时,只传递字节码。这意味着,为了能够有效地“逆向转换”回 API 调用,我们需要通过解释字节码来重新构造所有这些元数据。最终我们仍然是原封不动地传递字节码,但由于我们看不到原始的 DXContainer 文件,我们必须合成为主机端 DirectX 渲染器所期望的字段。这经过了大量试错,由 AI 助手完成了大部分工作,但它仍然是我们实现中最薄弱、最容易出错的部分。
## 主机渲染器
这是我们目前的结构:
客户机
主机
客户机 → 主机边界
应用程序
· DirectX / DXGI API 调用
系统库
· 对 Triton 的 DDI 调用
Triton
· DXBC → DXContainer → Neptune
Neptune UMD
· 序列化到环形缓冲区
KMD
· 向主机发送 VirtIO 命令
QEMU 主机
· 将调用交给 virglrenderer
Neptune 主机端
· 反序列化 → 主机 DirectX
主机 DirectX
· 渲染帧
1
2
3
4
5
6
7
8
1. 应用程序向系统库发起 DirectX 和 DXGI API 调用。
2. 系统库通过 DDI 调用调用 Triton。
3. Triton DDI 将原始 DXBC 字节码转换回 DXContainer,并向 Neptune 发起 DirectX 和 DXGI API 调用。
4. Neptune UMD 序列化 API 调用,并通过由 KMD 管理的环形缓冲区传递它们。
5. KMD 使用 VirtIO 接口向主机发送命令。
6. QEMU 主机处理命令,并将 Neptune 调用传递给 virglrenderer。
7. virglrenderer 中的 Neptune 主机模块反序列化 API 调用,并将它们转发给主机端的 DirectX 实现。
8. 主机端 DirectX 实现渲染帧。
让我们仔细看看最后一点。一旦 DirectX API 调用到达主机,我们仍然需要渲染它。当初我们在 Linux 上为 Wine 引入 Neptune 时,我们fork了 DXVK(https://github.com/osy/dxvk),以支持将交换链图像导出为 DMAbuf 资源。当时,我们决定在主机端实现交换链,以绕开共享纹理的问题。在内部,交换链图像被表示为纹理,但这些纹理很特殊,因为主机需要能够找到它们并用它们在屏幕上显示最终帧。我们的 Wine DXGI 库将所有交换链 API 调用直接转发给主机,因此主机“知道”哪些纹理将被用作后备缓冲。然后可以使用单独的 VirGL 命令将纹理 blob 扫出到虚拟机窗口中。这种方法以主机端 virglrenderer 进程中复杂的交换链逻辑,换取了客户机驱动的简单性和对 DXVK 的最小改动。
在开发 Triton 时,我们意识到主机端交换链处理是一个错误。在 Windows 中,DXGI 是一个与 UMD 通信的系统组件。DXGI 负责后备缓冲创建、帧节奏控制、模式切换等。UMD(在很大程度上)不会对 DXGI 给予特殊对待,因此我们那种将 DDI 调用“逆向转换”回 API 调用的方法在 DXGI 上并不真正适用。这意味着我们添加到主机端的所有交换链逻辑在很大程度上被绕过了。桌面合成器(DWM)在共享纹理上工作。一个进程的 DXGI 将内容渲染到自己的后备缓冲中,而后备缓冲与 DWM 进程共享,DWM 进程负责绘制桌面、窗口边框等。DWM 构建的最终帧会被用于扫出。这意味着,除了 DMAbuf 导出之外,我们还需要在 DXVK 中实现 DMAbuf 导入(不同的客户机上下文映射到不同的主机上下文)。一旦导入和导出都实现了,就不再需要主机端交换链逻辑了,因此为了让 Wine 驱动更加统一,我们将所有交换链逻辑移到了客户机端的 Neptune 驱动中。这一改动的另一个好处是,它更贴近 Venus 的设计方式,因此 virglrenderer 保持了干净整洁。
在 QEMU 上运行的 Windows 截图,主机为 Ubuntu(https://blog.getutm.app/assets/images/posts/triton/ubuntu-windows-desktop.png)
Windows DWM 合成在 QEMU KVM 上使用 DXVK 共享纹理正常工作,主机为 Ubuntu
## macOS
让 virglrenderer 在 macOS 上工作有一些挑战,但由于 Venus 现在可以在 macOS 上运行(https://gitlab.freedesktop.org/virgl/virglrenderer/-/merge_requests/1602),大部分后端挑战已经基本解决。剩下的任务是将 Neptune 连接到主机端的 DirectX 渲染器。有三个主要项目可以在 macOS 上处理 DirectX 的目标。然而,它们全部是以运行 Wine 为主要目标而设计的。它们缺少 Neptune 和 Triton 所需的共享纹理和共享栅栏(shared fences)功能。
### DXVK + MoltenVK
DXVK 是我们在 Linux 主机上使用的项目。它将 D3D11 API 翻译为 Vulkan API,然后使用主机 Vulkan 驱动进行渲染。在 Linux 上,这效果很好,因为 Vulkan 是一等公民,目前所有现代图形硬件都有不错的 Vulkan 驱动。但在 macOS 上,Vulkan 由另一个翻译层 MoltenVK 处理,它将 Vulkan API 翻译为 Metal API。在上一篇文章(https://blog.getutm.app/2026/introducing-neptune-direct3d-virtualization-for-qemu/#venus)中,我们谈到了让 DXVK + MoltenVK 工作的独特挑战,简单来说就是:不稳定,并且需要大量额外工作才能实现兼容性。
在打过补丁的 MoltenVK + Venus 上运行的古惑狼(Crash Bandicoot)(https://blog.getutm.app/assets/images/posts/neptune/macos-venus-dxvk.jpeg)
古惑狼在打过补丁的 MoltenVK + Venus + DXVK(客户机)上运行
### DXMT
DXMT 绕过了 Vulkan 的问题,将 D3D11 直接翻译为 Metal(D3D12 支持即将推出)。与 DXVK 一样,该项目主要是为
相似文章
OpenAI Blog
# 介绍 Triton:神经网络开源 GPU 编程 来源:[https://openai.com/index/triton/](https://openai.com/index/triton/)  我们发布了 Triton 1.0,这是一种开源的类 Python 编程语言,使没有 CUDA 经验的研究人员能够编写高效的 GPU 代码——在大多数情况下与专家能够生成的代码性能相当。
arXiv cs.AI
TRINE是一款单比特流FPGA加速器与编译器,用于端到端多模态推理,统一了多种层类型,并集成了运行时自适应计算模式、令牌剪枝和依赖感知的卸载功能,在20-21W功耗下相比RTX 4090实现最高22.57倍的延迟降低。
X AI KOLs Timeline
用户基准测试表明,Qwen 3.6 27B dense 模型(Q4 量化)能够在单张 RTX 3090 上通过单次提示自主生成一个完全可玩的多文件游戏,性能显著优于其前代版本,且无需任何人工干预。测试结果突显了在消费级硬件上本地代码生成和智能体能力方面的重大改进。
Hacker News Top
Collabora 报告了将面向 AMD GPU 的开源 RADV Vulkan 驱动从 Linux 移植到 Windows 的过程,通过逆向工程 WDDM2 接口成功运行了 Counter-Strike 2。
Fabien Sanglard
深入探讨创建 WinQuake(Quake 的 Windows 原生版本)的历史原因,以及它如何在 Windows 95 和 NT 上实现接近 DOS 版本的性能。