UTM выпустила Triton, драйвер DirectX 11 для QEMU
Команда проекта UTM (среда виртуализации на базе QEMU) выпустила Triton, новый Windows-драйвер, который вместе с ранее представленным протоколом Neptune даёт виртуальным машинам QEMU полноценную поддержку DirectX 11. Neptune, это слой переноса вызовов Direct3D через границу гипервизора поверх VirtIO; он уже позволял запускать Wine-игры на Linux-госте быстрее, чем через DXVK напрямую в госте, но это была лишь подготовка к главной цели, ускоренной графике для Windows-гостей.
Авторы объясняют, почему простой путь, подложить игре собственные d3d11.dll и dxgi.dll вместо системных (тот же приём использует связка DXVK, Vulkan и Venus), не годится. Во-первых, композитор рабочего стола Windows (DWM) видит кадр как обычную картинку и вынужден копировать его через CPU, поэтому о плавном рабочем столе речи не идёт (для полноэкранных приложений возможны отдельные трюки со scanout, но не для десктопа в целом). Во-вторых, d3d11.dll и dxgi.dll, системные компоненты, и подменить их напрямую нельзя без риска сломать Windows; даже если подмена сработает, игры с античитом, который такие модификации отслеживает, работать откажутся. В-третьих, копировать файлы в папку каждой отдельной игры неудобно пользователю. Поэтому правильный путь, по мнению команды, реализовать не сам API DirectX, а интерфейс драйвера DDI (Device Driver Interface).
В архитектуре Windows приложение обращается к системным библиотекам d3d11.dll/dxgi.dll, те передают уже упрощённый поток команд пользовательскому драйверу (UMD), который реализует DDI; UMD, в свою очередь, через DXGI связывается с драйвером уровня ядра (KMD), который управляет уже конкретным (в данном случае виртуальным) оборудованием. Часть с KMD для команды была уже решена: разработчики anonymix007 и arehnman независимо друг от друга писали KMD для Venus (реализации на основе Vulkan); поскольку Neptune создавался по образцу Venus, интерфейс между UMD и KMD у них практически идентичен. За основу для Triton команда взяла ветку anonymix007, в ней было реализовано больше возможностей.
Оставалась главная сложность, реализовать сам UMD с интерфейсом DDI для DirectX 11. Открытых примеров такой реализации на рынке фактически два. Первый, DirectX10 UMD в составе Mesa (открытая реализация OpenGL для Linux): он транслирует вызовы DirectX10 в вызовы Gallium, но в апстриме поддерживает только программную растеризацию, а недавняя экспериментальная поддержка VirGL не годится для хоста на macOS из-за ограничений тамошнего virglrenderer, тем не менее архитектура Mesa послужила команде понятным примером интеграции. Второй, единственный на сегодня рабочий открытый DirectX11 UMD есть у VirtualBox, но он устроен иначе: транслирует вызовы DDI в промежуточный байткод, который затем интерпретируется на стороне хоста обратно в вызовы DirectX. Команда Triton сознательно не стала перенимать этот подход по двум причинам: во-первых, по их наблюдениям (со ссылкой на форумные обсуждения, а не на собственное тестирование) такая двойная трансляция чаще приводит к багам и несовместимости игр; во-вторых, лицензия VirtualBox (GPLv3) юридически несовместима с MIT-лицензией virglrenderer и LGPLv2 у QEMU. При этом у VirtualBox команда позаимствовала две полезные вещи: список того, какие именно функции DDI обязательны для рабочей реализации (в документации Microsoft такого перечня нет), и понимание алгоритма подписи формата DXBC, который Microsoft нигде не публикует.
Ключевое техническое решение Triton, «обратное преобразование»: если d3d11.dll обычно превращает вызовы API в вызовы DDI, то UMD в Triton делает ровно противоположное, превращает вызовы DDI обратно в вызовы API DirectX. Это позволяет переиспользовать уже отлаженный и рабочий протокол Neptune вместо изобретения отдельного транспорта для DDI: на стороне хоста не нужен отдельный интерпретатор, потому что десериализованные команды Neptune и так представляют собой вызовы API DirectX 11. У VirtualBox цепочка длиннее, эмиттер и транспорт на госте плюс интерпретатор и диспетчер на хосте, и каждый лишний шаг добавляет задержку и риск ошибки. Большинство вызовов DDI при таком подходе транслируются в вызовы API почти напрямую, через сопоставление хендлов и различий перечислений API/DDI. Главная и самая трудоёмкая деталь, байткод шейдеров в формате DXBC (DirectX Byte Code), промежуточное представление, которое компилятор Microsoft (FXC) генерирует из языка шейдеров HLSL для версий DirectX до 12-й. Поскольку Triton делает обратное преобразование, ему не нужно самому разбирать и конвертировать этот байткод, существенное упрощение. Но есть нюанс: FXC вместе с байткодом обычно генерирует ещё и метаданные, которые потребляет d3d11.dll, а при вызове DDI приложению передаётся только сам байткод без этих метаданных. Значит, чтобы корректно воссоздать вызов API, Triton должен реконструировать недостающие метаданные, анализируя сам байткод, и только после этого передавать байткод хосту без изменений. По признанию авторов, эта реконструкция потребовала множества проб и ошибок и остаётся самой слабой и подверженной ошибкам частью реализации, большую часть этой работы выполнил ИИ-ассистент.
В итоге вся цепочка выглядит так: приложение вызывает API DirectX/DXGI → системные библиотеки вызывают DDI Triton → драйвер Triton восстанавливает из байткода DXBC полноценный DXContainer и делает вызовы API DirectX/DXGI к Neptune → UMD Neptune сериализует вызовы и передаёт их через кольцевой буфер, которым управляет KMD → KMD через VirtIO отправляет команды хосту → хост QEMU передаёт вызовы Neptune в virglrenderer → модуль Neptune на хосте десериализует вызовы и передаёт их хостовой реализации DirectX → та рендерит кадр.
Отдельный урок команда извлекла из работы со swapchain (цепочкой буферов кадра). Для Neptune/Wine она изначально реализовала логику swapchain на стороне хоста (с форком DXVK для экспорта буферов кадра как DMAbuf), чтобы обойти проблему общих текстур. При переходе к Triton выяснилось, что это было ошибкой: в Windows DXGI, системный компонент, который сам общается с UMD, и подход с обратным преобразованием DDI в API для DXGI толком не работает, поэтому вся хостовая логика swapchain фактически не используется. Композитор рабочего стола DWM работает с общими текстурами: один процесс рендерит кадр в свой буфер, а DWM использует этот же буфер как общую текстуру для сборки итогового изображения рабочего стола. Значит, помимо экспорта DMAbuf, нужно реализовать в DXVK ещё и импорт DMAbuf, поскольку разные контексты гостя соответствуют разным контекстам хоста; после реализации обоих направлений отдельная хостовая логика swapchain больше не понадобится. На этом моменте захваченный текст источника обрывается на середине описания дальнейшей работы над импортом/экспортом DMAbuf.
Ключевые факты
- Triton, новый Windows-драйвер (UMD+KMD), который реализует не подмену системных DLL, а сам интерфейс DDI DirectX 11; вместе с протоколом Neptune он даёт виртуальным машинам QEMU полноценную поддержку DirectX 11
- Ядерную часть (KMD) для Triton взяли из ветки разработчика anonymix007, написанной для Venus (Vulkan), её выбрали как более функциональную по сравнению с параллельной веткой arehnman
- Ключевая находка, «обратное преобразование» вызовов DDI в вызовы API DirectX, которое позволяет переиспользовать уже отлаженный протокол Neptune без отдельного транспорта для DDI, в отличие от подхода VirtualBox
- Самая сложная и, по признанию авторов, самая ненадёжная часть Triton, реконструкция метаданных байткода DXBC, которую в основном методом проб и ошибок реализовал ИИ-ассистент
- Команда сознательно не стала копировать подход VirtualBox (промежуточный байткод и интерпретатор на хосте) из-за возможных багов и несовместимости лицензий GPLv3 и MIT/LGPLv2, но позаимствовала у VirtualBox список обязательных к реализации функций DDI и понимание формата подписи DXBC
Почему это важно
QEMU долгое время не мог предложить нормальное GPU-ускорение для Windows-гостей именно из-за нехватки открытых реализаций драйверов уровня DDI, по словам авторов, это ниша, в которой разбираются считаные производители графического железа. Triton закрывает именно этот пробел: вместо перевода вызовов API в промежуточный байткод и обратно (как у VirtualBox) он делает прямое обратное преобразование DDI в API и переиспользует уже рабочий транспорт Neptune, что убирает лишний шаг трансляции и связанные с ним задержки и потенциальные ошибки.
Кому это важно
В первую очередь, разработчикам и участникам проектов QEMU и UTM, включая сценарий запуска Windows-гостей на хостах, где нативной поддержки Windows нет (например, на Mac с Apple Silicon через UTM). Также это интересно разработчикам открытых графических драйверов и виртуализации в целом, и в перспективе, пользователям, которым нужна работающая GPU-акселерация Windows-игр внутри виртуальной машины без хрупких обходных подмен системных DLL для каждой отдельной игры.
Как это применить
Triton и Neptune, открытые компоненты стека QEMU/UTM, построенные поверх VirtIO: со стороны хоста нужен QEMU с virglrenderer и хостовым модулем Neptune, со стороны гостя, сам драйвер Triton (UMD+KMD) вместе с доработками DXVK для импорта/экспорта DMAbuf. В тексте источника нет ни даты релиза, ни номера версии, ни инструкций по установке, судя по описанию, работа над частью, отвечающей за swapchain (импорт DMAbuf), на момент публикации ещё не была завершена.
Можно ли доверять
Источник, собственный технический блог команды UTM, написанный от первого лица с подробным разбором архитектуры; авторы прямо называют слабые места своей же реализации (в частности, реконструкцию метаданных DXBC), а не замалчивают их, что повышает доверие к изложению. В то же время в захваченном фрагменте текста нет ни одного числа с замерами производительности, ни списка проверенных игр, а сам текст источника обрывается на середине предложения до заключения, то есть итоговая картина и выводы статьи по этому фрагменту не проверяемы.
Риски и подводные камни
Сами авторы называют реконструкцию метаданных DXBC самой слабой и подверженной ошибкам частью реализации, она сделана методом проб и ошибок с помощью ИИ-ассистента, что оставляет риск скрытых багов в шейдерах. В источнике нет ни списка подтверждённо работающих игр, ни цифр производительности. Утверждение о проблемах совместимости игр в VirtualBox основано не на собственном тестировании авторов, а на форумных обсуждениях (сами авторы формулируют это осторожно, «похоже»). Про совместимость Triton с системами защиты от читов в источнике ничего не сказано, известно лишь, что прежний подход с подменой DLL с античитом не совместим. Юридическая несовместимость лицензий GPLv3 (VirtualBox) и MIT/LGPLv2 (virglrenderer/QEMU) также ограничивает, что команда вообще могла позаимствовать из существующих реализаций.
«Реконструкция этих метаданных потребовала множества проб и ошибок, с которыми справился ИИ-ассистент, но это по-прежнему самая слабая и подверженная ошибкам часть нашей реализации.»
— команда UTM, блог проекта