postfixnotation.org - blog

公開: 2020年10月18日 最終更新: 2020年11月15日 永続リンク

Windows 10上の東方紅魔郷のゲームスピードが異常高速化する問題および緩和プログラムthd3d8mitigationの作成

Windows 10上の東方紅魔郷のゲームスピードが異常高速化する問題の緩和プログラムthd3d8mitigationを開発しました。また、その際に問題を詳細に調査しました。推測が多いです。推測で書いている箇所は推測であることをできるだけ明示しています。

問題を理解した上でマイクロソフトにフィードバックしたいと思った方はここを参照ください。

1. 緩和プログラムの入手と注意点

そもそも、ウインドウモードであれば異常高速化しません。ただし、理想的な環境ではゲームスピードが少し速いです。緩和プログラムはフルスクリーンモードで遊びたい場合にのみ必要です。

緩和プロブラムthd3d8mitigationはここ(ソースコードはここ)からダウンロードできます。thd3d8mitigationのGitレポジトリはここ(ミラーはここ)にあります。ライセンスはMIT LICENSE

通常プレイの範疇外なので、節度を守って使ったり、本プログラムの使用を明記したりすることをおすすめします。また、本プログラムを適用した上で発生した問題を元のゲームの作者に報告するのは避けた方がよいでしょう。

2. 緩和プログラムの使い方

zip内のreadmeにも書いてあります。readmeもちゃんと読んでね。

  1. d3d8.dllとthd3d8mitigationcustom.exeをコピーして、ゲームの実行ファイルがあるフォルダーに貼り付けます。
  2. ゲームを起動します。
  3. お楽しみください。

環境によっては必要なプログラムが足りず起動に失敗することがあります。その場合はVisual Studio 2019 用 Microsoft Visual C++ 再頒布可能パッケージ (x86)をインストールしてみてください。https://support.microsoft.com/ja-jp/help/2977003/the-latest-supported-visual-c-downloads

高リフレッシュレートなゲーミングモニターを使っているなどの理由でリフレッシュレートが60FPSより高くなる環境では、併せて紅魔郷の強制60FPSモードを有効にしましょう。

3. 異常高速化問題の詳細

そもそも、なぜ紅魔郷が異常高速化してしまうのでしょうか?紅魔郷の仕様と問題の原因を探ってみましょう。

3.1. Direct3D 8

紅魔郷はDirect3D 8という描画ライブラリに依存しています。Direct3D 8はウインドウモードもしくはフルスクリーンモードで動作することができ、紅魔郷はDirect3D 8の機能を使ってウインドウやフルスクリーンで動作します。Direct3D 8はフルスクリーンモードのときのみ、垂直同期信号(単に垂直同期とも)を待機することができます[1]。ウインドウモード時はこの方法では垂直同期を待機できません。垂直同期信号とはモニターに描画するタイミングを知らせる信号のことです。多くのモニターは1秒ごとに60回(60Hz)描画します。紅魔郷はフルスクリーンモードの場合、原則としてこの垂直同期の待機機能を使ってフレームレートおよびゲームスピードを調整します。

3.2. 紅魔郷のフレームレートとゲームスピードの仕様

紅魔郷(に限らず東方)は60FPSが最適なゲームスピードにするためのフレームレートと考えられます。とりあえず遊べればよいという人であればフレームレートは多少前後しても問題ありませんが、よりシビアにゲームに臨んでいる人はほぼ60FPSぴったりを目指すことになるでしょう。それでは、条件によってフレームレートがどのように変わるのか順に見ていきましょう。

3.2.1. ウインドウモードの場合

ミリ秒精度の時間APIを使用してフレームレートを管理します。

理想的な環境では以前のWindowsおよびWindows 10ともに62.5FPSで動作します。60FPSにならないのはミリ秒精度の時間APIを使用してフレームレートを管理しているためです[2]。ただし、誤差が出るので、環境によってはゲームスピードがやや落ちて60FPS前後になることがあります。

3.2.2. フルスクリーンモードかつ設定で強制60フレームモードが無効な場合

Direct3D 8の垂直同期の待機機能でフレームレートを管理します。

以前のWindowsの場合、多くの環境では60FPSになります。高リフレッシュレートなゲーミングモニターを使用している環境などでは144FPSや240FPSになるはずです。

Windows 10の場合、Direct3D 8の垂直同期の待機が機能しないため、フレームレートは青天井に高くなり、ゲームスピードが異常高速化します

3.2.3. フルスクリーンモードかつ設定で強制60フレームモードが有効かつバージョンが(おそらく)1.02e以前の場合

おそらく、ウインドウモードの場合と同様に、ミリ秒精度の時間APIを用いて、理想的な環境では以前のWindowsおよびWindows 10ともに62.5FPSで動作します

私はバージョン1.02eの動作を確認できていないため、この動作は推測になります。しかし、体験版v0.13(v1.02e相当とされる)やv1.00ではこの動作になるため、v1.02e以前はすべて同様の動作をすると推測されます。

注: 現在CD-ROM媒体として出回っているバージョンは1.02fであるため、多くの人は該当しないでしょう。

3.2.4. フルスクリーンモードかつ設定で強制60フレームモードが有効かつバージョンが1.02f以降の場合

強制60フレームモードが無効な場合と違い、高リフレッシュレートなゲーミングモニターを使用している場合などでもゲームの初期化の際に強制的に60FPSで動作させる設定を試みます[3]。この方法でゲームの初期化に失敗すると、垂直同期の待機を止めた上で、ウインドウモードの場合と同様に、ミリ秒精度の時間APIを用いて(62.5FPSで)の動作を試みますが、現実的にはゲームの初期化に失敗してなおかつこの動作で無事ゲームが始まるケースは殆どありません。

以前のWindowsの場合、フレームレートは多くの場合60FPSになります。

Windows 10の場合、Direct3D 8の垂直同期の待機が機能しないため、フレームレートは青天井に高くなり、ゲームスピードが異常高速化します

3.2.5. ミリ秒精度の時間APIを使う方法の問題点

ウインドウモードやバージョン1.02e以前の強制60フレームモードは厳密には62.5FPSで、結果として、正常なフルスクリーンモードと比べてゲームスピードがやや速めになります。ウインドウモード時のゲーム進行を、ゲームスピードに左右されず流れるBGMと比べると、ゲームスピードの上昇が実感できます。たとえば4面道中の小悪魔が出現するあたりでBGMと演出がずれます。とりあえず遊べればよいという人にはあまり関係ない話ですが、よりシビアにゲームに臨んでいる人は気になることと思います。

3.2.6. フレームレートに応じたゲームスピード変化

紅魔郷はフレームレートをそのままゲームスピードに適応せず、フレームレートが高めな環境では弾の速度を落とすことでゲームスピードをできるだけ変化させないようにします[4]。しかし、楽しむ分には支障がでないという程度で、厳密なゲームスピードや弾の速さは幾らか変わってしまいます。とりあえず遊べればよいという人にはあまり関係ない話ですが、よりシビアにゲームに臨んでいる人は気になることと思います。実験によると、60~70FPSぐらいにかけてはゲームスピード補正が感じづらく、ゲームスピードが徐々に上がっていきます。70~190FPSにかけてはゲームスピード補正が効き、むしろゲームスピードが徐々に下がっていくようです。しかし、190FPSを上回ったあたりから再びゲームスピードが徐々に速くなっていき、数千FPSにもなると異常高速化します。ウインドウモードが常にミリ秒精度の時間APIを使用してフレームレートを管理することを考えると、高FPS時の異常高速化は紅魔郷の開発時点で既知の仕様だったと推測されます。

3.3. 異常高速化問題の原因

従来フルスクリーンモード時にのみ機能していた垂直同期の待機がWindows 10ではできなくなったためです。なぜ垂直同期の待機が機能しなくなったのかについてですが、Windows 10以降のDirect3D 8のフルスクリーンモードが実際にはウインドウモードを使った擬似的なフルスクリーンとして動作するようになった可能性が推測されます。紅魔郷はフルスクリーンモード時のフレームレートがDirect3D 8の垂直同期の待機機能に依存しているため、この待機機能が機能しないと待ち動作が無くなってフレームレートが青天井に高くなり、ゲームスピードが異常高速化します。

Windows 10以降のDirect3D 8のフルスクリーンモードが実際にはウインドウモードを使った擬似的なフルスクリーンとして動作するようになった(かもしれない)理由は不明です。Direct3D 8のフルスクリーンモードのコードを保守できる人がいなくなった、現代のビデオカードやOSの要件に対応し難かった、などが考えられるでしょうか。

4. 緩和プログラムの仕組み

緩和プログラムは紅魔郷に対してDirect3D 8として振る舞い、紅魔郷から特定の処理が呼び出されたら自前の処理を行ったあとに実際のDirect3D 8の処理を呼び出します(DLL Proxy)。そして、以前のWindowsでは垂直同期を待っていたタイミングで、実際に垂直同期を独自の方法で待つようにしています。

副作用は大きくないものの、潜在的な問題に当たる可能性も幾らかあります。また、今回は関係ないですが、Windows 10におけるDirect3D 8の非互換性問題の根本的な解決策にはなりません。

本プログラムは特定のゲームに依存しない汎用的なものです。特定のゲームの中身に関するコードは含まれていません。

5. 他の解決方法

これまでにも紅魔郷をWindows 10上で動作させようとする取り組みはいくつもありました。他の解決方法も見ていきましょう。

5.1. ウインドウを擬似的なフルスクリーンにするツール

紅魔郷はウインドウモードであればWindows 10上で正常に動作するため、ウインドウを擬似的なフルスクリーンにするツールでフルスクリーンにして遊ぶことができます。副作用は小さいか場合によっては無いと考えられます。ただし、元がウインドウモードなため、先述の通りゲームスピードが少し速くなります。

5.2. DirectX 9やWineなどの上で動作するDirectX 8互換レイヤー

根本的な解決方法のひとつである一方、潜在的な問題に当たる可能性はけして低くないでしょう。

6. 緩和プログラムを適用してもリプレイで会話パートが高速化するんだけど

確か紅魔郷の元来の仕様です。本問題とは多分関係ありません。以前のWindowsでも同様の動作をするはずです。

7. 永夜抄でも異常高速化するんだけど

現在CD-ROM媒体として出回っているv1.00bは異常高速化するようです。バージョンをv1.00dに上げると直ります。

8. マイクロソフトへのフィードバック

Windows 10でDirect3D 8のフルスクリーン時の垂直同期待機が機能しなくなっている問題は、このような問題再現用の小さなアプリケーションでも再現します。垂直同期待機が機能しなくなっている問題についてWindowsのフィードバック Hubに報告しましたが、いまのところ前進はありません。4ヶ月くらい経った気がするけど。有償のプロフェッショナルサポートに問い合わせましたが、プロフェッショナルサポートでは問題の解決を前進させるのが難しいとのことでした。その際に上位プランのユニファイドサポートであれば問題の解決を前進させることができるかもしれないという助言を受けましたが、費用の問題で個人では利用が難しいです。

もし問題の解決を前進させたいのであれば、まずは、フィードバック Hubに報告したり、私が報告したフィードバック(これ)から「類似するフィードバック」を送ったりするところから始めるのもひとつの手でしょう。

脚注

[1] 具体的には、IDirect3D8::CreateDeviceに渡すD3DPRESENT_PARAMETERS.FullScreen_PresentationIntervalにD3DPRESENT_INTERVAL_ONEなどを渡すとIDirect3DDevice8::Presentが垂直同期を待つようになります。ところが、Windows 10ではこれが待たなくなってしまっています。

[2] 1000(ms)/16(ms) = 62.5(fps)

[3] https://www16.big.or.jp/~zun/html/th06.htmlより引用:

ver 1.02f
・「強制的に60フレームにする」をチェックした場合、リフレッシュレートの変更を試みるようにした
・誤植とか修正
つまり、IDirect3D8::CreateDeviceに渡すD3DPRESENT_PARAMETERS.FullScreen_RefreshRateInHzに60を渡す。

[4] https://www16.big.or.jp/~zun/html/th06man/html/faq.html より引用:

Q14 フレームレートが60にならない

 このゲームは、そのマシンのリフレッシュレートより、フレームレートを決めます。
 ゲームの進行速度は、どのフレームレートでも同じ速度になるように調整されます。
 高速でプレイするようなことはありません。

 61以上になっている場合、60の時との違いは
 ・リプレイを保存できない。
 ・他人のリプレイが高速再生される。
 ・表示が滑らかになる。
 ・処理落ちがしやすくなる。
 です。

 リフレッシュレートが変更できない等で、どうしても60にしたい方は
 custom.exe を使用して、 「強制的に60フレームにする」をチェックしてください