it.todo("should be a (web)log");

役に立(つ/たない)技術情報やポエム

Windows on Linux で快適ゲーム生活

これは天久保 Advent Calendar 2023の6日目の記事です(遅刻。

天久保には居住していませんが「天久保」は町域とは関係ありませんので書きます。

Linux Gaming とその方法

Linux 環境でゲームをプレイすることは「どのように Linux 上で Windows のプログラムを動作させるか?」という問題と同じと言ってもよいでしょう *1。 なぜならほとんどの PC 向けのゲームは Windows のみにしかリリースされないからです *2

この互換性問題を解決するため、大きく分けて2つのアプローチが存在します。

  1. Windows API の互換レイヤーを用いて動作させる
  2. Linux 上で Windows仮想マシンとして起動させ、その上でプログラムを動かす *3

1 の互換レイヤーを用いた方法としては Wine やその Valve によるフォークである Proton が有力でしょう。 特に Proton は発展がめざましく、DirectX を Vulkan に変換する互換レイヤ*4などの恩恵もあり多くのゲームが快適に動作するようです*5

2 の仮想マシンを用いた方法は単純ではありますが本物の実装を用いるため(仮想マシンであることに気付かれないようにすれば)強力な方法です。

Proton と挫折

筆者も近年まではゲームを遊びたいときは Proton を用いていましたが、Wine であることを検知して動作を止めるゲーム*6や、演出としてウィンドウを操作するようなゲームがプレイできないなどの問題を抱えていました。 そんな状態で決定打となったのが Meta Quest2 *7を購入したことです。Linux における Quest 向け VR コンポジタの実装としては ALVR が存在しますが、当時(2020Q4)はまともに動作しなかった上、Linux における AMDGPU の動画エンコード支援の品質が信用できないものであったため、「Linux ネイティブ」の夢は諦めることになりました。

環境構築

材料紹介

  • GPU x2 (内蔵(iGPU)でも外付け(dGPU)でも可。Intel/AMDNVIDIA の組合せがおすすめだが今回は Intel + AMD)
  • CPU x1 (IOMMU に対応しているもの)
  • メモリ たくさん
  • ダミープラグ あれば

PCI パススルーを用いて GPU を完全に Windows ゲストに渡すため、GPU は2つ必要です*8。 この機能を利用するために CPU は IOMMU*9を持つものが必要です。マザーボードも対応している必要がありますが、どちらも最近のものなら問題ないでしょう。 メモリは割り当てた分は仮想マシンを起動している間持っていかれるので多く積まれているべきです*10。 CPU コアも数があると十分に割り当てても余るのであるとうれしいですがオーバーコミットできるのでメモリほどは問題になりません。 GPU は画面出力がないと動作してくれません。これを解決するのにダミープラグが便利です*11。もし無い場合はそこらへんに転がっているモニタの入力端子を使ってください。

仮想マシンの構築

ここで構築!といきたいところですが、PCI パススルーを用いた仮想マシンの構築方法についての情報はそこそこあるので紹介程度に留めておきます(基本はやるだけなので)。 いくつかハマりそうなところを書いておくとこんな感じ:

  • UEFI の設定で CPU の仮想化と IOMMU の機能を有効にする
  • 分離したいデバイスが独立した IOMMU グループになっていることを確認する(もしくはセキュリティリスクを考慮した上で緩和パッチを導入する)
  • vfio-pci モジュールが適切に設定されて GPU が分離されていることを確認する

ちなみに筆者は AMD CPU + AMD GPU のときに Resizable BAR が有効だと VM を起動しようとした瞬間にシステムがクラッシュする問題にハマりました。お前が始めた物語やろがい*12

PCI パススルーに成功すると Windows はパススルーした GPU の画面を認識してそちらもデスクトップとして使うようになります。 virt-manager のようなクライアントからだとこの画面を操作することができないため、GPU をパススルーする前に Windows をセットアップしておき、Parsecのようなリモートデスクトップを設定しておくと便利です。

Looking Glass を導入する

このまま Parsec を使い続けてもよいのですが、やはりほとんどのリソースを共有しているのにも関わらずネットワークを経由して接続されるのが気になります。 そこで Looking Glass を用いて Windows デスクトップを操作するようにします。VM とホストの間でメモリを共有することで超低遅延な画面共有を実現するらしいです*13。すごいですね。 クライアント自体にキーボード、マウス、クリップボードと音声を共有する機能も付いているのでこだわりがなければこれだけで事は足ります。遅延にこだわる場合は適当にデバイスをパススルーしてあげましょう。 例によってドキュメントがあるのでこれを参照して設定すればなんとかなります。共有メモリの量の計算が面倒なら 128MB とか書いておけばいいんです!*14

遊ぶ

この状態でも多くのゲームが遊べる状態ですが、アンチチートに引っ掛かって起動しないことがままあります。 この内 Easy Anti Cheat についてはなぜか VRChat が回避方法を書いています。 hyperv においてvendor_idを(適当に)指定し、sysinfodmidecodeを使って得た値を使って適切に設定することで回避できるらしいです。そうなんだ。

ファイルを共有する

QEMU / libvirt では virtio-fs を用いてゲストとホストの間でファイルシステムを共有できます。 しかしホスト側のデーモンである virtiofsd に VM との共有メモリを利用している場合に動作しないバグが存在しているため共有に失敗してしまいます。 歴史的経緯により virtiofsd には現在使われている Rust による実装と QEMU に存在した C による実装(以下 virtiofsd-qemu) があり、QEMU の実装では共有メモリを利用していてもファイル共有ができたようです。 virtiofsd-qemu は QEMU8 以降には含まれないため、QEMU7 をビルドします。

$ curl -LO https://download.qemu.org/qemu-7.2.6.tar.xz && tar -xf qemu-7.2.6.tar.xz
...
$ cd qemu-7.2.6 && mkdir build && cd build
...
$ ../configure --disable-strip \
    --disable-docs \
    --disable-gettext \
    --disable-sparse \
    --disable-guest-agent \
    --disable-guest-agent-msi \
    --disable-qga-vss \
    --disable-hax \
    --disable-whpx \
    --disable-hvf \
    --disable-nvmm \
    --disable-xen \
    --disable-vfio-user-server \
    --disable-dbus-display \
    --enable-tools \
    --enable-virtiofsd \
    --localstatedir=/var \
    --sysconfdir=/etc \
    --meson=meson \
    --enable-seccomp \
    --disable-tcg \
    --disable-kvm \
    --disable-gio \
    --disable-cfi \
    --disable-tpm \
    --disable-keyring \
    --disable-spice \
    --disable-spice-protocol \
    --disable-u2f \
    --disable-netmap \
    --disable-vde \
    --disable-vmnet \
    --disable-vnc \
    --disable-vhost-kernel \
    --disable-vhost-net \
    --disable-vhost-crypto \
    --disable-vhost-user-blk-server \
    --disable-virtfs \
    --disable-bochs \
    --disable-cloop \
    --disable-dmg \
    --disable-qcow1 \
    --disable-vdi \
    --disable-vvfat \
    --disable-qed \
    --disable-parallels
 ...
$ make # 並列数はいい感じに
...

ビルドが成功したら./build/tools/virtiofsd/virtiofsdにバイナリができているはずです。これを適当な位置に配置します。 virt-manager を使って virtiofs デバイスを追加した場合、XML は以下のようになっているはずです。

<filesystem type="mount">
  <driver type="virtiofs"/>
  <source dir="{共有するディレクトリ}"/>
  <target dir="{タグ}"/>
</filesystem>

この<filesystem>の中に<binary path="{virtiofsdのパス}"/>を追加します。 ここまででホスト側の設定は完了です。 次にゲスト側の設定をします。 WinFspvirtio-win-guest-tools が入った状態でVioFsSvcサービスを起動します。 うまくいくとZ:ドライブが出現し、その中のファイルが見えるはずです。 筆者は systemd-homed の作る loopback なホームディレクトリ内のディレクトリをマウントしようとしたら失敗した*15のでそこそこハマりどころがありそうです。

逆WSL2

まとめ

いかがでしたか? ゲームに限らず、すぐに使える Windows 環境を持っておくと役に立つこともあるはずです。 年の瀬は Linux Gaming でのんびりというのも悪くないんじゃないでしょうか。

明日(今日)は azarashi さんです。

*1:これは Windows ではない環境全て、例えば macOS にも当てはまります

*2:逆に macOS 向けも出る場合なぜか Linux 版が存在する確率も高いような気がしている。作者がマルチプラットフォームに気持ちがあるということ?

*3:細かいことを言えばもう1つのアプローチとして「ハイパーバイザの上に WindowsLinux を両方載せ、リソースを共有する」という方法があります(GPU-PV)が、独断と偏見(触ったこと、無し)により紹介していません

*4:https://github.com/doitsujin/dxvk

*5:https://www.protondb.com/。他にも AMDGPU における Vulkan ドライバにも改良が行われていたりと Valve (SteamDeck) の恩恵は非常に大きい

*6:Nantoka Impact

*7:当時は Oculus だった……

*8:こんなことを書くと virtio-gpu とか Intel VT-g とか SR-IOV とかあるじゃないかと言われそうですがここでは深入りしないことにします

*9:Intel VT-d や AMD-Vi

*10:多くて困ることはほぼない、皆さんも 32GB くらいはあるでしょう?

*11:モニタの入力部だけみたいなデバイス

*12: Resizable BAR は AMD Smart Access Memory のこと

*13:さらにホストが Intel/AMDGPU を使っている場合 KVMFR カーネルモジュールを導入することで一部のデータがゲストから GPU のバッファに直接コピーされる。オトク!

*14:4K SDR での要求値

*15:たぶん mountns の周辺で問題が起きている