プログラミング
Triton: QEMU向けのDirectX 11ドライバー
Triton: DirectX 11 Driver for QEMU (blog.getutm.app)
要約
本記事では、QEMU仮想マシンでWindowsゲスト向けのグラフィックスアクセラレーションを実現する新しいWindowsドライバー「Triton」を紹介します。Tritonは、既存のNeptuneレイヤーと連携し、DirectX 11の完全なサポートを提供します。これにより、従来のDLL差し替え方式の課題であったパフォーマンスの低下や互換性の問題を解決し、よりネイティブに近いグラフィックス体験を目指します。
全文翻訳
前回の記事では、VirtIO向けのDirect3Dプロトコル転送レイヤーであるNeptuneを紹介しました。Neptuneにより、ハイパーバイザー境界を越えてDirect3D APIコールをシリアライズできるようになり、Linuxホスト上でLinuxゲストのWineゲームを、ゲスト内でDXVKを直接使用するよりも高速に実行することが可能になりました。確かに、その時点での成果はそれほど劇的ではありませんでしたが、私たちの真の目標である、Windowsゲスト向けの最新グラフィックスアクセラレーションの基盤を築きました。私たちは今、Tritonと呼ばれる新しいWindowsドライバーを構築することでこれを達成しました。TritonはNeptuneと連携し、QEMU仮想マシンにDirectX 11の完全なサポートをもたらします。
macOS上のQEMUで仮想化されたWindows 11 ARM64で実行中のCrash Bandicoot Trilogy (x64)
Tritonとは何か?
NeptuneがDirect3D APIコールをシリアライズでき、WindowsがDirect3Dを使用しているなら、もう完了ではないかと疑問に思うかもしれません。Direct3DがWineで動作するなら、Windowsでも動作するはずですよね?結局のところ、Wineとは何でしょうか、Windowsエミュレーターでなければ?
短い答えは、ある程度は可能です。Neptune Mesaドライバーは、Direct3D APIセットを完全に実装するd3d11.dllとdxgi.dllをビルドします。そのため、これらのファイルをゲームの実行可能ファイルと一緒に配置するだけで、Windows自身のドライバーの代わりにロードされ、その方法でいくつかのゲームを実行できるようになります。これは、DXVK→Vulkan→Venusを使用してアプリケーション内でDirect3Dをローカルで実行する以前の試みでも行われたアプローチです。
このアプローチにはいくつかの欠点があります。第一に、そして最も重要なことですが、ウィンドウコンポジター(DWM)はフレームを画像として「認識」するため、GPU画像バッファを正しいウィンドウ位置にコピーするためにCPUのブリッティングを使用する必要があるため、良好なパフォーマンスを得ることができません。フルスクリーンアプリケーションではネイティブにスキャンアウトするためのトリックを使用できるかもしれませんが、スムーズなデスクトップ体験を得ることは決してできません。第二に、d3d11.dllとdxgi.dllはWindowsのコアコンポーネントであるため、システムファイルを直接置き換えてもWindowsが引き続き機能することを期待できません。たとえそれが機能するように管理できたとしても、この種の変更を特に検出するアンチチートを備えた多くのゲームをプレイできなくなります。そのため、DLLをロードできるのはアプリケーションごとに限られ(互換性は変動します)。
これが最後の点につながります。グラフィックスアクセラレーションを機能させたい各アプリケーションにファイルをコピーする必要があるのは、ユーザーフレンドリーな体験ではありません。
正しいアプローチは、DirectX APIを実装することではなく、DirectX DDI(Device Driver Interface)を実装することです。
ユーザーモードアプリケーション Direct3D 11 (d3d11.dll) ユーザーモードドライバー (DDI) DXGI (dxgi.dll) カーネルモード カーネルモードドライバー ハードウェア / 仮想化
Windowsでは、アプリケーションはシステムのDirect3DおよびDXGIライブラリと通信します。d3d11.dll(および古いバージョン)は、状態追跡の複雑な作業を行い、DDIを実装するユーザーモードドライバー(UMD)に、よりクリーンなコマンドストリームを送信します。アプリケーションは、グラフィックスアダプターの初期化、スワップチェーンの設定などを行うためにdxgi.dllとも通信します。UMDはDXGIを介してカーネルモードドライバー(KMD)と通信します。KMDはグラフィックスベンダー(私たち)によって実装され、実際のハードウェア(またはこの場合は仮想ハードウェア)を駆動します。
Wineの場合、カスタムd3d11.dllとdxgi.dllを実装してAPIコールをインターセプトしましたが、Windowsでは代わりにUMDとKMDを実装する必要があります。したがって、課題は、DirectX DDIインターフェースを持つUMDを実装し、VirtIOデバイスと通信するKMDとのプライベートインターフェースをセットアップすることです。
幸いなことに、後者の部分はすでに解決されています。anonymix007とarehnmanは、独立してVenus(Vulkan)用のKMDに取り組んでいました。Vulkanは完全に独立したグラフィックスAPIであるため、DirectX DDIを実装する必要はなく、そのUMDは「d3d11.dllを置き換える」アプローチに似ており、KMDと直接通信してQEMUにコマンドを送信します。NeptuneはVenusをモデルにしているため、高レベルのカーネルインターフェース(DMA、コマンドバッファなど)は非常に似ており、UMDとKMD間のインターフェースはまったく同じです。最終的に、anonymix007のブランチをベースとして選択しました。なぜなら、彼らの実装はKMD側でより多くの機能が動作していたからです。
これにより、難しい部分が残ります。DirectX 11のDDIを実装する必要があります。
新しいシステムを設計する際には、以前に同様の問題をどのように解決したかを理解することが常に賢明です。残念ながら、参考になるオープンソースのDDI実装はあまりありません。Windowsグラフィックスドライバーは非常にニッチなトピックであり、専門家のほとんどは数少ないグラフィックスハードウェアベンダーのいずれかで働いています。これが、QEMUがWindows GPUアクセラレーションでこれまであまり進歩できなかった理由の1つです。
幸いなことに、私たちには学ぶことができる2つの動作するオープンソース実装があります。第一に、MesaにはDirectX 10 UMDがあります。前回の記事を読んでいない場合、要点は、MesaがLinuxのOpenGLを実装しているということです。MesaはOpenGLの状態追跡を行い、Gallium APIコールを発行します。Mesa DirectX 10 UMDは、同じGallium APIコールを発行するOpenGLの代替手段です。その後、Galliumバックエンドドライバー(AMD、Intel、VirGLなど)がそれらをネイティブグラフィックスドライバーAPIに変換します。アップストリームのMesaはDirectX 10のソフトウェアラスタライゼーションバックエンドのみをサポートしていますが、VirGLで動作させるための最近の作業がありました。残念ながら、macOSのvirglrendererは、このUMDが必要とする多くの機能をサポートしていないため、macOSホストのグラフィックスアクセラレーションを取得する実行可能な方法ではありません。しかし、Mesaコードベースとの統合は、Tritonを統合するためのクリーンな例を提供してくれます。
VirtualBoxには、動作する唯一のオープンソースDirectX 11 UMDがあります。しかし、このドライバーを私たちの用途に「採用」することはできません。彼らのドライバーの動作方法は、DDIコールを中間バイトコードに変換し、その後ホスト側でバイトコードを解釈してDirectX APIコールに変換することです。このバイトコードエミッターとインタープリターをQEMUに移植することは(AIの助けを借りて)簡単かもしれませんが、いくつかの理由から私たちはそれを避けました。第一に、DDIをバイトコードに変換し、そのバイトコードをDirectX APIにリフトアップするというこの変換は、ゲームとの互換性を制限する可能性のあるバグにつながる可能性があると考えています。実際、オンラインフォーラムのスレッドを読むと、多くのゲームがこの理由でVirtualBoxで実行されないようです。翻訳エンジンに機能不足やバグがあった場合、修正には多くの積極的なメンテナンス作業が必要となり、私たちはOracleに依存したくありません。
第二に、VirtualBoxのGPLv3とvirglrendererのMITライセンスまたはQEMUのLGPLv2の間にはライセンスの非互換性があります。VirtualBoxのコードを統合することはできませんが、そこから貴重な洞察を得ることができました。実装されたDDIプロトタイプのリストとエラーを返したプロトタイプのリストは、動作する実装の最小要件として使用されます。この情報はMSDNドキュメントからは容易に入手できず、すべてのプロトタイプを実装しようとすると、範囲が非常に大きくなります。彼らのDXBC署名アルゴリズムも理解するのに役立ちます。なぜなら、Microsoftはそれをどこにも公開していないからです。
DDIコールの中間転送フォーマットを使用するというVirtualBoxのアプローチを取りたくなかったので、より良いことができます。d3d11.dllをDirectX APIコールをUMD DDIコールに変換するコンポーネントと大まかに考えると、私たちのUMDに期待することは、DDIコールをDirectX APIコールに変換することです。
これはなぜ便利なのでしょうか?なぜなら、新しいトランスポートを考案することなく、テスト済みの動作するNeptuneプロトコルを使用できるからです。ホスト側では、これらのコールを実行するために追加の作業を行う必要はありません。VirtualBoxはゲストにエミッターとトランスポートレイヤー、そしてホストにインタープリターとディスパッチャーを必要とします。各ステップはレイテンシーを追加し、エラーや非互換性を導入する可能性を高めます。ゲストにはまだエミッターとトランスポートが必要ですが、ホスト側ではインタープリターは必要ありません。なぜなら、デシリアライズされたNeptuneコマンドはDirectX 1