GamescopeとProtonでbeatmania IIDX INFINITASを120 fps動作させたところ、画面上はGREATのタイミングで押せており、押鍵からキー音までの遅延も感じないのに、BGMに対してキー音が音楽的にずれて聞こえることがあった。違和感が強いときの差は1フレーム程度ではなく、体感で少なくとも50〜100 msだった。
同じGamescope、Proton、Wine prefix、120 Hz表示、PipeWire経路を維持し、DXVKのD3D9設定だけを比較したところ、d3d9.presentInterval=0と1には明確な体感差があった。DXVK_CONFIG=d3d9.presentInterval=1では違和感が解消した可能性が高い。
このA/B結果から、運用上はVSyncを有効にするのが正しい。一方、INFINITASはclosed sourceなので、内部で何が同期したのかは直接確認できていない。現時点では、BGMを進める音声クロックと、譜面・判定を進めるゲーム側クロックが独立しており、VSync OFF時に後者のペーシングがずれる、という仮説が最も観測と整合する。
調査した環境と切り分けの前提
この現象を確認したのは、infinitas.lanでGE-Proton11-5-infinitas-cleanを使い、Gamescopeを必須とした構成である。INFINITASは1920×1080、120 fpsで動作し、Gamescopeが3840×2160、120 Hzで外側のScrollへ表示していた。
調査時点の音声はWineのPulseAudio互換経路からPipeWireのkonaste-sinkへ出しており、実行中processには次の値が入っていた。
PULSE_SINK=konaste-sink
PULSE_LATENCY_MSEC=60INFINITASのBGMとキー音は、PipeWireへ届く前に同じゲームprocess内で混合される。このため、PULSE_LATENCY_MSECやsinkのbufferは、混合後の音声全体を遅らせることはあっても、BGMだけ、またはキー音だけを50〜100 msずらす説明にはなりにくい。また、押鍵からキー音までの遅延を感じなかったことも、単純な出力buffer過大説とは合わない。
WMAの先頭無音や末尾欠落はGE-Proton側で別に修正した問題である。今回のA/BではWMA実装を変えず、present intervalだけで体感が変わったため、WMAのデコード遅延とも分けて考えている。現在のPipeASIOを使う低レイテンシ構成については、Linuxでコナステ音ゲーの低レイテンシとOBS収録を両立したにまとめた。
想定している内部構造
INFINITAS内部を概念的に分けると、次のような構造になっていると思われる。
WMA/BGM decoder ──> audio clock ───────────────> BGM output
│
│ 本来は同じ楽曲位置を指す
│
display/pacer ─────> game/chart clock ──> note表示・判定
│
controller input ─────────────────────────────┤
└──> keysound request ──> audio mixerBGMはオーディオデバイスが要求するsample数に従い、44.1 kHzの連続した音声クロックで進む。一方、譜面位置、ノート表示、判定時刻は120 Hzのゲームループまたはフレームtickで進んでいる可能性がある。
この二つが50〜100 msずれると、プレイヤーは画面上のGREATに合わせて正しく押せる。入力は現在のゲーム側譜面位置に対して判定され、その場でキー音の再生を要求するので、押鍵から発音までの反応も速い。しかし、その瞬間にBGMだけが別の楽曲位置へ進んでいれば、GREATで鳴らしたキー音がBGMに対して音楽的に外れて聞こえる。
つまり「入力からキー音までが遅い」のではなく、「画面と判定が参照する曲位置」と「BGMが実際に再生している曲位置」が一致していない、という説明である。
VSync OFFで差が広がる可能性
DXVKのD3D9 presentInterval=0では、ゲームのPresentは垂直帰線を待たないIMMEDIATE系の経路になる。INFINITASがPresentの戻りをフレーム境界として利用している場合、VSync OFFでは自前のtimerやsleepで120 Hzを作る必要がある。
高精度timerを使っているだけなら、必ず50〜100 msずれるわけではない。毎フレーム、絶対時刻との差を計算して補正していれば、個々のwake-upが少し遅れても長期誤差は累積しない。問題になるのは、例えば次のような実装である。
- 約8.333 msの相対waitを繰り返し、実際に超過した時間を次のwaitで補正しない
- 実時間から譜面位置を計算せず、1 tickごとに譜面時刻を固定量だけ進める
- 遅れて起床した際のtick欠落や重複を、BGM側の絶対位置へ再同期しない
timeGetTimeのような粗い値と120 Hzの端数を組み合わせ、丸め誤差を残す
WineのQPCはCLOCK_MONOTONIC_RAWを基にしており、ホストもTSC clocksourceを使っていた。したがって、QPCそのものが15.6 msや1 ms単位に劣化している証拠はなかった。疑うべきなのは時計を読む精度より、VSync OFF時の待機、wake-up、固定step更新、遅延補正の方法である。
参考として、ゲームを停止した状態でLinuxの相対select(8.333333ms)を1,200回繰り返すと、指定時間に対して約87.9 msの累積超過が生じた。ただし、これはINFINITASが同じ待ち方をしている証拠ではない。50〜100 ms級の差が単純な相対waitでも生じ得ることを示すhost baselineにすぎない。
50〜100 msを2分間で累積すると仮定した場合、必要な系統誤差は約0.042〜0.083%であり、120 Hzの1フレーム当たりでは約3.5〜6.9 µsにすぎない。平均fps表示が120のままでも、この程度の長期的な速度差は見えない可能性がある。
presentInterval=1が変えるもの
d3d9.presentInterval=1では、DXVKがD3D9のsync intervalを1にし、通常はVulkanのFIFO present modeを選ぶ。FIFOではPresent側にbackpressureが生じるため、ゲームループが外部の表示周期から継続的なペーシングを受ける。
ただし、DXVKのPresentが物理monitorのvblankを直接待つわけではない。今回のnested Wayland構成では、同期の鎖は概ね次のようになる。
INFINITAS / DXVK
│ FIFOでゲーム用surfaceへPresent
v
Gamescope
│ Wayland presentation feedback / backpressure
v
Scroll / wlroots
│
v
物理monitor 120 HzDXVKが直接同期する相手はGamescopeが提供する仮想的なpresentation surfaceであり、その先をGamescopeとScrollが物理monitorへ渡す。このため、ゲームframeと物理vblankの位相が常に完全一致するとは断定できないし、途中のqueueによる固定遅延もあり得る。それでも、長期的な120 Hzの周波数とbackpressureをゲームループへ返し、自前timerだけで走ったときの累積差を抑える働きは期待できる。
KONAMIが公式FAQでVSync ONを案内していることも、INFINITASが表示同期を単なるtearing防止以上の前提としている可能性と整合する。
MangoAppがIMMEDIATEでも矛盾しない
Gamescopeの--mangoappで表示されるpresent_mode=IMMEDIATEは、INFINITASのDXVK swapchainを示していなかった。MangoAppはbm2dx.exeへ注入されたMangoHudではなく、Gamescope内で動く別processであり、独自のVulkan surfaceを持つ。
したがって、MangoApp自身がIMMEDIATEで表示されていても、ゲーム側DXVKがpresentInterval=1でFIFOを選ぶこととは矛盾しない。ゲーム側の設定は、実行中のbm2dx.exeの環境変数、DXVK log、またはゲーム自身のswapchainで確認する必要がある。
今回の正常側MangoApp logでは、直近60秒の平均frametimeが約8.3334 msで、平均としてはほぼ120 fpsだった。一方で約4 msと12 msの提出間隔が交互に現れていた。しかし、これはpresentInterval=1で違和感が改善した側のlogであり、MangoAppの計測点も物理表示時刻ではない。失敗側の原因を示す証拠には使えない。
現時点で退けた説明
調査中に考えたが、現在は採用していない説明もある。
- 119.93 Hz表示そのものによるドリフト: Gamescope内部のXWayland modelineには119.93 Hzが見えたが、WineからWin32アプリへは整数の120 Hzとして報告されていた。実際のペーシング問題は残り得るが、119.93というmetadataだけを原因にはできない。
- 1フレーム未満から1フレーム程度の位相差: 実際の違和感は最低でも50〜100 msであり、8.33 msの固定表示遅延だけでは説明できない。
- MangoAppのIMMEDIATEはゲームもVSync OFFという意味: MangoAppとゲームは別process・別surfaceなので成立しない。
- PipeWire bufferだけがBGMとキー音を分離してずらす: PipeWireへ来る前に両者が混合され、押鍵から発音までの遅延も感じなかったため、主説明にはなりにくい。
確度と残っている確認
高い確度で言えるのは、同じ実行経路でd3d9.presentInterval=0/1だけを変えたとき、音楽的な同期感に明確な差があり、1が実用上の改善条件だったことである。
その内部原因が、曲開始時のBGMと譜面時計の固定offsetなのか、曲中に増える累積driftなのかは判別できていない。BGMとキー音のどちらが先行しているかも、体感だけでは特定できなかった。そのため「VSync OFF時の自前ペーサーでゲーム側時計が音声時計からずれる」は最有力仮説ではあるが、確定した原因ではない。
さらに確認するなら、Gamescopeを外さず、同一曲の最初から最後までpresentInterval=0と1をそれぞれ記録し、違和感が開始時から固定なのか、時間とともに増えるのかを比較したい。失敗側のframe数と経過実時間を取得できれば、frame数 × 8.333333 msとの差も計算できる。ただし、MangoAppの時刻をそのまま物理表示時刻やゲーム内部時計とみなしてはいけない。
常用設定としては、内部原因の確定を待たず、次を維持する。
DXVK_CONFIG=d3d9.presentInterval=1これはKhronosのVulkan present mode仕様、DXVKのD3D9 swapchain実装、DXVKのpresent mode選択、Gamescopeの境界から説明できる運用結果である。ただし、INFINITAS内部の時計構造については、ここまでの観測から組み立てた推測として扱う。