QEMUの仮想マシンでDirectX 11が動く。オープンソースドライバTritonの全貌

Mac上のWindows仮想マシンで3Dゲームが動くまでの技術的道のりと、AIが担った役割を読む

QEMUの仮想マシンでDirectX 11が動く。オープンソースドライバTritonの全貌
getutm

Mac上のWindows仮想マシンで3Dゲームが動くまでの技術的道のりと、AIが担った役割を読む


DLLをコピーする時代の終わり

QEMUの仮想マシンでWindowsゲームを動かす。言葉にすれば一行で済むが、ここに至るまでが長かった。

仮想マシンの中のWindowsはGPUを持たない。ホスト(実機)のGPUはハイパーバイザーの向こうにあり、ゲストOSからは触れない。QEMU上のWindowsはソフトウェアレンダリングに頼るしかなく、3Dグラフィックスはまともに動かなかった。

この壁を越える最初の試みが、UTM開発者のosy氏が2026年前半に発表したNeptuneだ。Direct3D APIの呼び出しをVirtIO経由でホストに転送し、ホスト側のGPUで描画する。Linuxゲスト上のWineではDXVKより速く動いた。

だがWindowsゲストでは使いものにならなかった。Neptuneが生成するd3d11.dlldxgi.dllをゲーム実行ファイルの横にコピーすれば動くゲームもある。しかしWindowsの画面合成器DWMはこのDLLの存在を知らない。DWMにとってフレームはただの画像データでしかなく、GPUバッファからウィンドウへの転送にCPU経由のピクセルコピーが挟まる。パフォーマンスは出ない。

さらにd3d11.dlldxgi.dllはWindowsの中核コンポーネントだ。システムファイルを差し替えればOSが壊れる。アンチチートはこの種の改変を検出する。結局、アプリごとにDLLをコピーして祈るしかない。

osy氏はこの限界を見据えて、DirectX APIではなくDDI(Device Driver Interface)を実装する方向に舵を切った。Windowsが正規のグラフィックスドライバとして認識する層で動かす。その成果が、7月24日に公開されたTritonだ。

DDIからAPIへの「逆変換」

Tritonの設計思想は、一言で言えば逆変換だ。

Windowsのグラフィックスは層構造になっている。アプリケーションがDirectX APIを呼ぶと、d3d11.dllがステートトラッキングを行い、整理されたコマンド列をUMD(ユーザーモードドライバ)のDDIに渡す。UMDはDXGI経由でKMD(カーネルモードドライバ)と通信し、KMDが物理GPUを駆動する。

Tritonはこの流れの中でUMDとして振る舞い、受け取ったDDI呼び出しをDirectX API呼び出しに戻すd3d11.dllがAPI→DDIの変換を行い、TritonがDDI→APIの逆変換を行う。一周して元に戻ったAPI呼び出しは、既に動作実績のあるNeptuneプロトコルでホストに送られる。

この設計は、VirtualBoxの方式と対照的だ。VirtualBoxのDirectX 11ドライバはDDI呼び出しを中間バイトコードに変換し、ホスト側でそのバイトコードをDirectX API呼び出しに再解釈する。ゲスト側にエミッタ、ホスト側にインタプリタが必要になり、変換ステップが多い分だけ遅延とバグの余地が増える。

フォーラムではVirtualBoxでまともに動かないゲームの報告が絶えない。ライセンスも合わない。VirtualBoxはGPLv3、QEMUはLGPLv2、virglrendererはMITだ。

Tritonの逆変換方式なら、ホスト側に追加のインタプリタは不要だ。Neptuneがデシリアライズした結果がDirectX 11 API呼び出しそのものであり、解釈を挟まず実行できる。変換ステップが1つ少ない分だけ、バグの入る余地が減る。

DDI呼び出しの大半にはAPI側の対応物があり、逆変換はハンドルのマッピングとenum値の読み替え程度で済む。最も厄介だったのはDXBC(DirectX Byte Code)シェーダの処理だった。

DXBCの再構築(AIが担った試行錯誤)

DXBCはMicrosoftのシェーダコンパイラFXCが出力する中間コードだ。FXCはバイトコードに加えて入出力シグネチャなどのメタデータも生成し、ひとまとめにしたDXContainerをアプリに渡す。d3d11.dllはメタデータを消費した上で、DDIにはバイトコード部分だけを渡す。

つまりTritonがDDI→APIの逆変換を行うとき、消えたメタデータを自力で再構築しなければならない。バイトコードを解釈してシグネチャ情報を合成し、ホスト側のDirectXレンダラが受け入れるDXContainerを組み立てる。バイトコード自体はそのまま渡すが、器を作り直す必要がある。

osy氏はこの作業を「大量の試行錯誤」と表現し、AIアシスタントが処理したと書いている。UTMブログのタグにはclaude-opus-4-8の記載がある。開発記録の別記事は「Three weeks of maxing out Fable」(Fableを3週間限界まで使い倒した記録)と題されており、Claude Opus 4.8とClaude Fableの両方が開発に投入されたことがうかがえる。

このDXBCメタデータ合成が、Tritonの実装で最も脆弱な部分だとosy氏自身が認めている。Microsoftはシグネチャのアルゴリズムを公開しておらず、VirtualBoxの実装から推測するしかなかった。

macOSでの3つの選択肢

ゲストからホストにDirectX API呼び出しが届いた後、ホスト側で実際にレンダリングを実行するバックエンドが必要になる。Linux上ではDXVKがVulkanに変換すればよい。macOSでは事情が異なる。

DXVK + MoltenVK: LinuxホストではDXVKがD3D11→Vulkan変換を担う。macOSではさらにMoltenVKがVulkan→Metal変換を重ねる。二重の変換層を経るため不安定で、osy氏は「まだ多くの作業が必要」としている。

DXMT: D3D11をVulkanを飛ばしてMetalに直接変換する。UTMチームがWine向けの設計をネイティブmacOSライブラリとしてフォークした。FireStrike(x64、Windows 11 ARM64ゲスト)のスコアは8920

D3DMetal: AppleがGame Porting Toolkit向けに開発したD3D11/D3D12→Metal実装。DXBCからApple独自のシェーダ中間表現AIRへのトランスパイラを含む。クローズドソースであり、x86_64バイナリしか存在しないため、virgl_render_serverプロセス全体をRosettaで実行する必要がある。それでもFireStrikeスコアは1万1534。ネイティブARM64で動くDXMTを大きく上回る。

macOSホストレンダラの比較
DXVK +
MoltenVK
DXMTD3DMetal
変換経路D3D11→
Vulkan→Metal
D3D11→MetalD3D11→Metal
中間層Vulkan
(MoltenVK)
なしなし
FireStrike未計測89201万1534
実行環境ARM64ARM64x86_64
(Rosetta)
ソースオープンオープン非公開
利用制限なしなしゲーム評価
目的に限定
UTM同梱不可
※FireStrikeはx64版、Windows 11 ARM64ゲストで実行。DXVK+MoltenVKは安定性の問題でスコア未取得

ただしD3DMetalのライセンスは「Apple製品向けゲームの開発・テスト・評価」と「非商用配布」に限定されている。UTMに同梱することはできない。CodeWeaversのCrossOverがD3DMetalをバンドルしているのは、Appleとの個別契約があるためだとosy氏は述べている。

共有テクスチャの壁(Apple Siliconだからできた解法)

macOSでのTriton動作には、プロセス間でGPUテクスチャを共有する仕組みが必要だった。virglrendererはレンダラコンテキストごとに別プロセスを立てる設計で、ゲストの各D3Dコンテキストが別々のvirgl_render_serverプロセスに対応する。

プロセスが分離していればレンダラのクラッシュがVM全体を巻き込まない。だがプロセスをまたいでGPUリソースを共有する手段が要る。

AppleのAPIにはMTLSharedTextureHandleやIOSurface、CALayerHostなどの選択肢があるが、いずれもvirglrendererが使うファイルディスクリプタベースのSCM_RIGHTSハンドル受け渡しとは相性が悪い。

この問題に突破口を開いたのは、macOSへのWayland移植に取り組んでいた@Drakulix氏のアイデアだった。shm_open()で共有メモリオブジェクトを作り、それをMetalのMTLBufferにゼロコピーでマッピングする。

Apple SiliconのUMA(統合メモリアーキテクチャ)ではCPUとGPUが物理アドレス空間を共有している。両方のプロセスから同じメモリ領域にCPUでもGPUでもアクセスできる。リニアテクスチャしか使えない制約はあるが、共有テクスチャの数が少なければ問題にならない。

同期も工夫が要る。GPU描画は非同期であり、プロデューサプロセスAが描画中にコンシューマプロセスBが合成を始めればティアリングが起きる。本来はMTLSharedEventHandleでGPU間フェンスを共有すべきだが、これにはXPCが必要になる。

Tritonではエミュレートフェンスで対処した。プロデューサがClearUnorderedAccessViewUintを使い、共有メモリにタイムライン値をGPU上で書き込む。GPU上の他の描画コマンドと順序が保証されるため、この値が書き込まれた時点で描画は完了している。

コンシューマ側はCPUで共有メモリをポーリングし、値の変化を検知してから合成を開始する。CPUポーリングによるレイテンシは発生するが、フレーム完了境界でのイベントに限られるため実用上は許容範囲だ。

Crash Bandicootが動くまでの距離

UTM v5.0.4 Betaが8月1日に公開され、Tritonが実験的に搭載された。Windows 11 ARM64ゲスト上でCrash Bandicoot Trilogy(x64)がmacOS経由のQEMU仮想化環境で動作している。

ドライバはまだ不安定で、osy氏自身が「重要なVMにはインストールしないでほしい」と警告している。ゲームの互換性も限定的だ。署名済みのプリビルトドライバが公開されており、ソースコードはQEMU、virglrenderer、DXMT、Windows UMD/KMDのすべてがオープンソースとして利用できる

ブログ記事の末尾にはこう書いてある。「AIにこのページを指差して、セットアップを頼め」。開発にAIを使い、ドキュメントの消費にもAIを前提とする。ドライバを作る側も使う側も、前提が変わり始めている。

QEMUの仮想マシンでDirectX 11が動く。その一文の裏には、DDI逆変換、DXBCメタデータ合成、プロセス間テクスチャ共有、GPUフェンスエミュレーション、そしてAppleのライセンス制約という、何層もの技術的判断が重なっている。

Parallelsは商用ライセンスで手早く解決した問題を、オープンソースは一つひとつ自力で積み上げた。その積み上げの中に、AIが自然に組み込まれていた。

関連記事

この記事を共有する