Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

Linux版のSteamでWindowsのゲームをする話(主にGNOME+Wayland環境)

最近、SteamをLinuxにインストールしてWindowsのゲームを遊んでいます。Steamのインストール自体はそんなに困ることはありませんでしたが、コントローラーやパフォーマンスのところでいくつか悩んだところがあったので忘れないように記事にします。グラフィックスやハードウェア関連は本当に素人なので勘が働かなくて難しかったですね...

手元の環境

2022年12月現在、私物では2021年のVAIO Z勝色特別仕様にArch Linuxを入れて使っています。セットアップは12インチMacBookにArch Linuxをインストールしたときの内容とほとんど同じですが、ゲームに関係ありそうな要素はこの辺りでしょうか。

  • GNOME
  • Wayland
  • PipeWire+WirePlumber

Steamのインストール

Steamを動作させるには32bitアプリケーションのサポートが必要です。Arch Linuxではmultilibリポジトリから各種32bitパッケージのインストールが可能ですが、あまり環境を汚したくないのでFlatpakでSteamをインストールしました。

$ flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
$ flatpak --user install flathub com.valvesoftware.Steam
$ flatpak --user install flathub com.valvesoftware.Steam.CompatibilityTool.Proton

手順としてはこれだけです、簡単ですね。Steamで販売されているゲームの一部はLinuxに対応していますが、基本的にはWindowsのゲームを動かすことになると思うのでProtonも必要になります。OpenGLやVulkanのAPI(org.freedesktop.Platform.GL32.default)は、Steamが依存しているライブラリとして一緒にインストールされるので気にする必要はありません。

また、Steamと一緒にVAAPIもインストールされますが、これはVideo Accelaration APIらしいです。

Steamの起動

Arch LinuxでFlatpakをインストールするとXDG_DATA_DIRSPATHが通っているので、Flatpakでインストールしたアプリケーションは自然とGNOMEのアプリケーション一覧に出現します。そのため通常はランチャーからSteamを起動すればいいのですが、コマンドラインからSteamを起動する方法も知っておくと便利です。

$ flatpak run com.valvesoftware.Steam [-steamdeck] [-gamepadui]

上記コマンドの-steamdeck-gamepaduiは省略可能です。これらのオプションを与えると、ゲームパッドでも操作できるモードでSteam自体が全画面で実行されます。ゲームコンソールを起動した直後のイメージですね。

SteamはXWaylandを使って実行されているようです。XWaylandを使うプログラムかどうかは、xlsclientsコマンドなどで確認できます。

$ sudo pacman -S xorg-xlsclients
$ xlsclients
lufia-pc  gsd-xsettings
lufia-pc  ibus-x11
lufia-pc  gnome-shell
lufia-pc  steam

Protonを有効にする

Protonはインストールしただけでは有効になっていません。この状態では、Windows用のゲームを起動するボタンが無効になっており起動できないので、Steamのメニューから、Steam→Settings→Steam Play→Advancedと画面を遷移すると

  • Enable Steam Play for all other titles

という項目があるので、 Proton 7.0 を使うように設定してください。バージョン番号のところは、新しいものがあればそちらを使う方がいいかもしれません。

ここまで終われば、Windows用のゲームをLinuxでも遊べるようになっています。

ゲームパッドの設定

まず、Windowsゲームでの主要な入力方式はDirectInputとXInputの2種類あります。DirectInputよりXInputの方が新しいので、コントローラでXInputが使えるならこちらを使う方が好ましいでしょう。Linuxでは、コントローラデバイスからの入力はドライバを通してゲームパッドエミュレーションに渡され、ゲームからはエミュレーションによってXboxコントローラとして認識されるようです。

ものによっては何もしなくても動作するのかもしれませんが、所有しているアーケードコントローラ(リアルアーケードPro.V サイレントHAYABUSA、以下RAP5)は、Steamには認識されていてボタンのコンフィグなどでも反応するけれど、ゲームを起動するとレバーもボタンも動かない状況でした。RAP5はDirectInputとXInput両方の実装があって、筐体横のスイッチで切り替えます。PCモードに切り替えるとXInputの実装が使われます。

次にSteamをコマンドラインから起動すると、

Couldn't initialize virtual gamepad: Couldn't open /dev/uinput for writing

というエラーが出力されていたので、udev で書き込みを許可しておく必要があることが分かります。

inputグループにwrite権限を追加

AURには game-devices-udev パッケージが存在しています*1が、こんなに多くは必要ないので自分で必要なだけ追加しましょう。グループは何でもいいのですが、トラックパッド指紋認証など入力デバイス関係の権限は input グループに与えているので、ここでも input グループに与えることにしました。

$ sudo homectl update --member-of=input <username>

# 他のグループにも所属している場合はカンマ区切りで与える
$ sudo homectl update --member-of=wheel,input <username>

次に udev のルールを書くのですが、まずはデバイスのIDを調べましょう。journalctlでログを出したままコントローラを差し込むと以下のようなログが出力されます。

$ journalctl -f
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: new full-speed USB device number 32 using xhci_hcd
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: New USB device found, idVendor=0f0d, idProduct=00b1, bcdDevice= 1.14
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: Product: Real Arcade Pro.V HS
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: Manufacturer: HORI CO.,LTD.
 8月 11 17:11:04 lufia-pc kernel: usb 3-6.3.1: SerialNumber: 55DE86DB
 8月 11 17:11:04 lufia-pc kernel: input: Generic X-Box pad as /devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.3/3-6.3.1/3-6.3.1:1.0/input/input40

ここで出力された idVendoridProduct を控えておき、udev のルールを書いていきます。このルールはSteam Controller known issues and platform-specific notesを参考に、必要なところだけ抜粋しました。

# /etc/udev/rules.d/99-rap5.rules

# This rule is needed for basic functionality of the controller in Steam and keyboard/mouse emulation.
SUBSYSTEM=="usb", ATTRS{idVendor}=="28de", MODE="0666"

# This rule is necessary for gamepad emulation.
KERNEL=="uinput", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"

# Valve HID devices over USB hidraw
KERNEL=="hidraw*", ATTRS{idVendor}=="28de", MODE="0666"

# HORI RAP5
KERNEL=="hidraw*", ATTRS{idVendor}=="0f0d", ATTRS{idProduct}=="00b1", MODE="0660"

保存した後は sudo udevadm control --reload で反映されると思ったのですが、動かなかったのでOSごと再起動しました。これで上記のエラーが消えて、RAP5がゲーム中でも使えるようになりました。

一般的なゲームパッドの情報は以下も参考にしてください。

eGPUを買う

VAIO Zは、IntelのCPUに統合されたGPU(Integrated GPU、以下iGPU)であるIris Xeグラフィックスを持っていますが、ゲームによっては非力さが否めません。例えばiGPUでGGSTをプレイすると、ヒットエフェクトが激しい場面では30FPS程度まで落ちてしまいます。

近年では、USB Type-CケーブルでThunderboltとして外付けのGPUを接続することで、重い描画処理をオフロードすることができるらしいので、外部GPU(External GPU、以下eGPU)の筐体とAMDのグラフィックボードを購入しました。今回購入したものは以下の2つです。

VAIO ZはThunderbolt 4に対応しているのですが、現在まだThunderbolt 4対応のケースは無いようです。Thunderbolt 4搭載端末にeGPUを接続してベンチマークを取ってみたによると、帯域としてはThunderbolt 3も4も同じらしいので、大きな支障はないだろうと判断してThunderbolt 3接続のケースを買いました。また、NVIDIAのグラフィックボードは情報量は多いけれどもLinuxで使うときのトラブルも多そうだったので、AMDのボードで頑張ったら買える価格のものを選びました。

HDMI/DP/USB Type-CでeGPUに接続した場合の音声

eGPUから外部ディスプレイに接続する場合の音声はどういった経路で送られるのか不明でした。USB Type-Cでディスプレイに接続していたときは外部ディスプレイのスピーカーを使っていたけれど、eGPUから外部ディスプレイにHDMI/DisplayPort/USB Type-Cのいずれかで接続したとき

  • 音声はどの経路で送られることになるのか
  • スピーカーを別途用意する必要はあるのか

が気になりました。これの答えは、HDCP対応のグラフィックボードであれば、OSからはグラフィックデバイスサウンドバイスの2つが追加されたように認識されるので、サウンドの設定で切り替えるとeGPUからディスプレイへ音声が送られることになります。

以下の出力で、0000:3d:00.1 という識別番号でオーディオコントローラが見えていますね。

$ lspci -k | sed -n '/Radeon/,+7p'
0000:3d:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 24 [Radeon RX 6400/6500 XT/6500M] (rev c1)
    Subsystem: ASRock Incorporation Device 5227
    Kernel driver in use: amdgpu
    Kernel modules: amdgpu
0000:3d:00.1 Audio device: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21/23 HDMI/DP Audio Controller
    Subsystem: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21/23 HDMI/DP Audio Controller
    Kernel driver in use: snd_hda_intel
    Kernel modules: snd_hda_intel

eGPUとPCの接続方法

現在、PRIMEと呼ばれる技術を使って、一部の描画処理だけをiGPUから別のGPUへ切り替えることができるようです。これはNVIDIAではOptimusと呼ばれているものに相当します。

Linuxのハイブリッドグラフィックス実装はVGA Switcherooサブシステムで行われているようです。

PRIMEが有効な環境の場合、特に指定しなければプライマリGPU(ほとんどの場合はiGPU)で描画を行います。ゲームや3Dのレンダリングなど描画の負荷が高いアプリケーションをeGPU側で描画させるには、環境変数DRI_PRIME=1を設定してプロセスを起動するだけです。

# iGPUで実行する場合
$ glxgears

# eGPUで実行する場合
$ DRI_PRIME=1 glxgears

GPUが意図通りに切り替わっているかどうかは、以下のコマンドなどで確認ができます。

$ sudo pacman -S mesa-utils
$ glxinfo -B | grep 'OpenGL render'
OpenGL renderer string: Mesa Intel(R) Xe Graphics (TGL GT2)

$ DRI_PRIME=1 glxinfo -B | grep 'OpenGL render'
OpenGL renderer string: AMD Radeon RX 6500 XT (navi24, LLVM 14.0.6, DRM 3.48, 6.0.8-arch1-1)

この他にも、GNOMEを使っている場合はgnome-control-center-print-rendererを使っても同じ内容を確認できます。

$ /usr/lib/gnome-control-center-print-renderer
Mesa Intel(R) Xe Graphics (TGL GT2)

$ /usr/lib/gnome-control-center-print-renderer
OpenGL renderer string: AMD Radeon RX 6500 XT (navi24, LLVM 14.0.6, DRM 3.48, 6.0.8-arch1-1)

またPRIMEでは、

  • eGPUにオフロードした描画結果をノートPCに備え付けのディスプレイに出力する
  • eGPUにオフロードした描画結果をeGPUに接続している外部ディスプレイへ出力する

といったことが可能です。従って、ノートPCとeGPUを接続する場合、以下のどちらかを選択することになるのではないでしょうか。

  • PCとeGPUを接続する、eGPUから外部ディスプレイへ接続する
  • PCとeGPUを接続する、またPCから外部ディスプレイへも接続する(eGPUは外部ディスプレイには接続しない)

私は後者の接続方法を選択しています。理由は下のほうにも書いていますが、2022年12月時点では前者の接続方法ではパフォーマンスの問題があると分かったからです。

SteamのゲームをDRI_PRIME=1で実行する

SteamのゲームをeGPUで描画させるには、ゲームのタイトルを右クリックしたメニューのプロパティから「起動オプション」を探して、以下の内容に変更します。

DRI_PRIME=1 %command%

プライマリディスプレイとは

GNOMEの場合、「設定」→「ディスプレイ」と遷移するとプライマリとなるディスプレイを変更できます。プライマリにしたディスプレイにはGNOME Shellのトップバーなどが配置されます。

プライマリGPUとは

プライマリディスプレイとは別で、プライマリGPUという存在もあります。プライマリGPUGNOMEの設定から変更することはできませんが、確認はできます。「設定」→「このシステムについて」を開いたときにグラフィックとして記載されているGPUがプライマリGPUです。

現在のプライマリGPUを調べる方法は、「設定」以外にもいくつかの方法があります。有名なところだと gnome-control-center-print-renderer コマンドまたは glxinfo コマンドがあります。

$ /usr/lib/gnome-control-center-print-renderer
Mesa Intel(R) Xe Graphics (TGL GT2)

$ sudo pacman -S mesa-utils
$ glxinfo -B | grep 'OpenGL render'
OpenGL renderer string: Mesa Intel(R) Xe Graphics (TGL GT2)

ところで、プライマリGPUはどういった存在なのでしょうか。GNOME+Wayland環境の場合、GNOMEのWaylandコンポジタであるMutterはプライマリGPUでウィンドウなどを描画します。

セカンダリGPUからディスプレイへ出力したときの性能

少し上で、

2022年12月時点では「PCとeGPUを接続する、eGPUから外部ディスプレイへ接続する」の接続方法ではパフォーマンスの問題がある

と書きました。

一部の描画をPRIMEによってeGPUに切り替えたとき、切り替えた描画それ自体はeGPUで行われます。だけどもディスプレイに描画するためには、他のウィンドウと重ねて1枚の画像として出力する必要があります。そのためeGPUが描画した結果をプライマリGPU側へ戻して、他のウィンドウと合成する必要がありますが、eGPUからディスプレイに接続している構成では、プライマリGPUで合成した結果をさらにeGPU側へ送り、ディスプレイへ送信するコストが発生します。

手元で試した限りでは、iGPUからディスプレイへ接続する場合は60FPSを維持するけれど、eGPUからディスプレイへ接続した場合では55FPS前後まで落ちてしまっていたので、その方法は諦めました。

ただ、DMA-BUF Feedback という仕様が実装されつつあり、これが使えるようになれば上記どちらの構成でもパフォーマンスの問題は無くなるかもしれません。GNOMEは42で対応されて、MESAは22.3で対応されましたが、FlatpakでインストールしたSteamはまだMESA 22.1.7であったので使えないようです。

GNOME(Wayland)でプライマリGPUを切り替える

プライマリGPUはデフォルトだとiGPU側が選ばれますが、設定によって変更することは可能です。もちろんeGPU側をプライマリGPUにすると合成もeGPU側で行えるため、eGPUで描画してeGPUに接続したディスプレイへ出力したときのパフォーマンス問題は解消します。

udev で適切なタグを設定すると、GNOMEのプライマリGPUを変更できます。例えば /etc/udev/rules.d/61-mutter-primary-gpu.rules

ENV{DEVNAME}=="/dev/dri/card1", TAG+="mutter-device-preferred-primary"

とした状態でログイン時にeGPUが認識されていると、プライマリGPUとして/dev/dri/card1*2が採用されます。card1の名前は相対的なものなので、必要ならlspci -kなどで識別番号を調べて、その識別番号を/dev/dri/by-path/pci-xxxから探してください。

$ lspci -k | grep VGA
0000:00:02.0 VGA compatible controller: Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics] (rev 03)
0000:3d:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 24 [Radeon RX 6400/6500 XT/6500M] (rev c1)

$ ls -l /dev/dri/by-path/pci-0000:3d:00.0-card 
lrwxrwxrwx 1 root root 8 12月  6 22:30 /dev/dri/by-path/pci-0000:3d:00.0-card -> ../card1

タグが適切に設定できているかどうかは、以下のコマンドで確認できます。

$ udevadm info --query=all /dev/dri/card1 | grep TAGS
E: TAGS=:seat:mutter-device-disable-client-modifiers:master-of-seat:uaccess:mutter-device-preferred-primary:
E: CURRENT_TAGS=:seat:mutter-device-disable-client-modifiers:master-of-seat:mutter-device-preferred-primary:uaccess:

これで再起動の後にゲームを実行するとeGPUがプライマリGPUとして使われて、DRI_PRIME=1しなくてもeGPU側で描画できるようになりますが、プライマリGPUを変更した場合は以下のデメリットが存在します。

  • 常にeGPUが使われるので電力消費が大きい
  • eGPUを外すことができない(外すとWaylandセッションが切れるのでログイン画面に戻ってしまう)

消費電力はまあ少し我慢すればいいのですが、ケーブルを抜くたびにログイン画面に戻ってしまうのは不便なため、プライマリGPUを変更することは諦めました。

Sway(wlroots)でプライマリGPUを切り替える

試していませんが、Swayなどwlrootsを使っているコンポジタの場合は、WLR_DRM_DEVICES環境変数で変更できるようです。

WLR_DRM_DEVICES=/dev/dri/card1:/dev/dri/card0

X11でプライマリGPUを切り替える

こちらも試していませんが、xrandrの設定でPrimaryGpuオプションを設定すると変更できるようです。

トラブル事例

eGPUを抜くと画面が固まって操作できなくなる

eGPUを接続した後でケーブルを抜くと、GNOMEの画面が固まったまま操作ができなくなる事象がありました。この場合、sysfs経由で事前にeGPUを取り除いておくと、ケーブルを抜いても固まらなくなります。

まずlspciGPUの識別番号を調べておきましょう。以下の場合は 0000:3d:00.0 が識別番号となります。

$ lspci -k | grep -A 3 VGA
0000:3d:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 24 [Radeon RX 6400/6500 XT/6500M] (rev c1)
    Subsystem: ASRock Incorporation Device 5227
    Kernel driver in use: amdgpu
    Kernel modules: amdgpu

この番号が特定できたらremoveファイルに1を書き込みます。

# 要root
echo 1 >/sys/bus/pci/devices/0000:3d:00.0/remove

これでケーブルを抜いても固まることがなくなります。ついでに、上記コマンドで取り外したあと再接続したい場合は scan ファイルに1を書けば実現できます。

# 要root
echo 1 >/sys/bus/pci/rescan

eGPUがスリープ状態になる

eGPU側にディスプレイを接続せずDRI_PRIME=1で一部の描画だけを行っている場合、eGPUがアイドル状態になるとスリープに入ってしまって、それ以降デバイスが起きるまで使えなくなる事象が発生しました。このスリープはカーネルパラメータにamdgpu.runpm=0を与えると抑止できます。

--- /boot/loader/entries/arch.conf.orig   2022-02-12 22:05:41.680559575 +0900
+++ /boot/loader/entries/arch.conf 2022-11-28 20:15:16.856666803 +0900
@@ -2,4 +2,4 @@
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
-options luks.name=ef718fc5-8eb5-44e8-904b-34c3705d3390=cryptroot root=/dev/mapper/cryptroot rw
+options luks.name=ef718fc5-8eb5-44e8-904b-34c3705d3390=cryptroot root=/dev/mapper/cryptroot rw amdgpu.runpm=0

amdgpu.runpm以外のカーネルオプションは以下にまとまっています。

USB Type-Cケーブルを流れるプロトコルは色々ある

USB Type-Cケーブルはケーブル自体の規格であって、中を流れるプロトコルは1つではありません。

PCからUSB Type-Cケーブルを直接ディスプレイへ接続すると、その中を流れる通信はDisplayPort ALT Modeプロトコルです。おそらくディスプレイの仕様に DisplayPort ALT Mode といった記述があると思います。だけどもPCとeGPUをUSB Type-Cケーブルで接続すると、この中を流れる通信はThunderbolt 3または4プロトコルです。こちらもeGPUの仕様にThunderbolt 3などの記述があるのではないかと思われます。

当然ですが、USB機器を接続した場合はUSBのプロトコルが流れます。ケーブルを接続したときにネゴシエートしているのだと思いますが...難しいですね。

HDMIケーブルで接続するとリフレッシュレートが30Hzに落ちる

HDMIのケーブルには複数の規格があり、HDMI 1.2のケーブルは最大1920x1200(60Hz)です。このケーブルを4Kディスプレイへ接続すると、解像度またはリフレッシュレートのどちらかが犠牲となります。HDMI 1.3のケーブルは2008年ごろから市場に出ているようですが、それより古いケーブルを使うときはバージョンに注意しましょう。

FlatpakがNVIDIAのドライバを認識しない

Flatpakが認識しているドライバは--gl-driversオプションで調べられます。AMDIntelGPUを使う場合は、特に気にする必要はありません。

$ flatpak --gl-drivers
default
host

NVIDIAのドライバをインストールした場合はnvidia-375-26のようなドライバが追加されて、環境変数FLATPAK_GL_DRIVERSでドライバを切り替えられるそうですが、NVIDIAGPUは持っていないので手元では試していません。

*1:これはfabiscafe/game-devices-udevをコピーするものらしい

*2:/dev/dri/by-path/pci-xxxのIDなどから適切なデバイスを選んでください