
2026/10/12 15:46
Show HN: PS2 ゲームを静的にリコンパイルして WebAssembly にする
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
画期的なプロジェクトが標準的な Web ブラウザ内で PlayStation 2 ハードウェアを成功裏にエミュレートし、Gran Turismo 4(PAL)のようなレガシータイトルを統合型 GPU で Chrome 上でフルスピードの 50fps で再生することを可能にした。C++への Ahead-of-time リコンパイルと WebAssembly を連携させ、WebGPU/WebOS の再実装を採用することで、システムは PS2 アーキテクチャの主要な特性である 128 ビットレジスタ、分岐遅延スロット、非 IEEE 浮動小数点挙動(無限大・NaN なし、不準正規数_FLUSH_)、ベクトルユニットタイミングおよび MAC パイプライン遅延、またスイッズルドテクスチャやパレットのようなグラフィックレイアウトの特別な処理などの PS2 アーキテクチャの特性を維持している。
ポートのデモは 2 つある:Gran Turismo 4 はブラウザメモリカードに保存され、God of War II ではベクトルユニットタイミング、I/O プロセッサ、関連コードパスにおいてリコンパイラへのバグ修正が不可欠だった。PS2 カーネルは EE 側に対して高レベルのスレッド/セマフォでエミュレートされ、その I/O プロセッサは C++ で再実装されており、ネイティブ IRX モジュールを HTTP Range リクエスト経由で読み込むことができる。グラフィック処理は専用のグラフィックスレッド上の WebGPU コンピュータシェーダーによって担当され、4 MB のビデオメモリ内で 3×まで解像度をサポートする。SPU を介した音声処理は 48 つの ADPCM ボイスを支持し、共有メモリ出力は AudioWorklet 経由で行う。MPEG-2 ムービーは IPU 実装によってデコードされる。エミュレーションはワーカースレッドで実行され、ベクトルユニット 1 とグラフィックコマンドはリングバッファを使ってページスレッドと通信するために別々のワーカーで処理される。GT4 における計算ディスパッチをテクスチャアップロードのホイスティングと最近アップロードされたコンテンツの 16 MB のキャッシュによって最適化することで、約 650 から約 80 に削減した。
リコンパイルツール(
eerecomp)は 16 KiB ヌントで実行ファイルを処理し、R5900 のすべての特性を維持する。ポートスクリプト(port.sh, serve.py)は自動的に制御フローを探査し、欠落したエントリーポイントを追加し、マイクロプログラムを SIMD C++ に翻訳し、ISO イメージから直接 Web ページを構築する。初期設定には Linux x86-64 環境と現代的な C++20 コンパイラ(clang が推奨)、CMake、Ninja、Python 3 およびネイティブランナー用の SDL2(オプション)が必要である。将来の作業はゲームライブラリの拡大およびキャッシュとグラフィックの最適化のさらなる微調整に焦点を当てる。本文
PlayStation 2 ゲームをブラウザ上・エミュレーターなしで動作させる仕組み
PlayStation 2(PS2)ゲームを、従来のエミュレーターソフトを使用せず、Web ブラウザ上で直接動作させる技術です。本プロジェクトは、ゲームの Emotion Engine(EE)マシンコードを C++ に再コンパイルし、WebAssemblyへ変換することで実現しています。
- 処理の流れ: PS2 のマシンコード → C++ コード → WebAssembly
- 利用技術: WebGPU、PS2 のオペレーティングシステム(OS)、ハードウェアの再実装
- 姉妹プロジェクト:
と同様の手法を採用(当初のタイトルは「グランツーリスモ 4」)。psp-web-recomp
動作確認と性能について
フルな録画映像はこちら(1 分 24 秒)。
グランツーリスモ 4
PS2 のハードウェアに最も負荷がかかるタイトルの一つであり、以下の特徴があります。
- ベクトルユニット: 両方のユニットを有効活用。
- 厳密な動作: リプレイやライセンス認証時の浮動小数点数挙動、パレット読み込み等のグラフィックトリックを維持。
- 完全なゲーム体験:
- 起動からオープニングムービー、メニュー、レースまで音楽・効果音付きで動作。
- ブラウザ上のメモリアルカードへセーブが可能。
- 性能データ (PAL リリース):
- レースは全速力(1 秒間に 50 フレーム)で進行。
- Chrome ブラウザ + ノートパソコン統合 GPU の場合、PS2 よりも**高い解像度(2 倍)**でのレンダリングが可能。
ゴッド・オブ・ウォー II
同じスクリプトで使用せず動作確認済みです。ゲーム固有のコード追加は不要ですが、GT4 で検出されなかったバグや IOP の欠落箇所を発見しました。
- 戦闘シーン: メニューからカットシーンを経て戦闘へ移行し、激しい戦闘時も PS2 解像度で毎秒 60 フレームを維持。
- 高解像度対応: 解像度を 2 倍にしても、ノートパソコンの統合 GPU 限界内であれば毎秒 48〜60 フレームで動作可能。
動作原理
再コンパイル(Recompilation)
eerecomp ツールはゲームの実行ファイルを読み取り、以下の処理を行います。
- 制御フロー追跡を行い、関数を特定。
- ゲストコードを16KiB 単位で C++ コードに変換・出力。
- R5900 CPU の特性保持:
- 128 ビットレジスタとメディア命令のサポート。
- ジャンプ遅延スロットのシミュレーション。
- IEEE 標準との異なる丸めルールや浮動小数点特殊値(無限大・NaN など)の実装。
- ベクトルユニット 0のプロセッサとしての扱い。
- スタック最適化: ユニット間のジャンプをテイルコールに変換し、ホストスタックの巻き戻し処理により、ゲストスレッドごとに独立したスタックメモリを使用しない設計に。
小型の PS2 カーネル
OS の EE 側と IOP(入出力プロセッサ)側は高レベルで再実装されています。
- EE 側: スレッド管理、セマフォ、DMA コントローラー、Vblank インターハント、SIF チャンネルなど。
- IOP 側: ゲーム固有の IRX モジュール、サウンドドライバー、ディスクストリーミングコード、コントローラー/メモリアルカードドライバーが R3000A CPU インタープリタ上で動作。
- ライブラリ再実装: IOP カーネルとモジュールが呼び出すライブラリは C++ で再実装。
- ディスク読み込み: 初期段階で可執行ファイルのみをダウンロードし、本体データは HTTP Range リクエストを通じてストリーミング取得。
ベクトルユニット
PS2 の 3D 計算の多くはベクトルユニット 1 上のマイクロプログラム(マイクロコード)によって処理されます。
- パイプライン遅延モデル化: MAC パイプライン、ステータスおよびクリップフラグの挙動をシミュレート。
- 浮動小数点ルール: PS2 の特殊値(無限大・NaN なし、非標準形式は 0 丸め、オーバーフローは最大値クランプ)に従う。
- 精度の差異: 算術演算自体はホスト側なので、コンソールの「切り捨て」とホストの「丸め」の違いにより最終ビットで微小な差異が生じることがある。
- 即時翻訳: テストランで見つかったマイクロプログラムは、SIMD C++ として事前にコンパイルされ、初めて遭遇するプログラムについては WebAssembly モジュールへ即座に翻訳。
グラフィックス(GS シンセサイザ)
4MB のビデオメモリを持つ GS は GPU に直接対応します。
- WebGPU Compute シェーダー: 三角形をタイルに分割し、GS 固有のテクスチャリング・パレット読み込み・アルファブレンド・深度テストなどをラスタライズ。
- 完全なコンソール再現: すべての処理が GS メモリ内で行われるため、テクスチャ書き出しやフレームバッファのパレット解釈など、コンソール上の挙動を完全に再現。
- 解像度選択: 内部解像度をネイティブ(1 倍)、2 倍、3 倍から選択可能。
スレッドと並列処理
- エミュレーション全体はワーカー(Worker)内で実行され、ベクトルユニット 1 も専用のワーカーを確保(コンソールの並列動作と同様)。
- GS コマンドは WebGPU デバイスを所有するページスレッドへリングバッファ経由で送付。
サウンドとムービー
- SPU2: 48 ボイスの ADPCM、ADSR エンベロープ、音楽用ストリーミング DMA を再現し、共有メモリ経由で AudioWorklet に出力。
- ムービー: PS2 の IPU(Internal Processor Unit)である MPEG-2 デコーダーを実装してデコード。
高速化と最適化の教訓
初期実装ではソフトウェアラスタライザを使用しており、フレームレートは3 fpsでした。以下のような改良により劇的な向上を達成しました。
- GPU シェーダー移行:
- GS を WebGPU Compute シェーダーへ移行し、ベクトルユニットプログラムをコンパイルすることで、約 **70 fps(ネイティブ)**に向上。
- Compute ディスパッチ削減:
- 初版では毎フレーム約 650 回の呼び出しでしたが、三角形バッチの分割などを統合化し、約80 回に削減。
- GPU キャッシュ活用:
- GT4 のレースシーン(毎フレーム 3MB テクスチャアップロード)において、単純なスキップは不可能ですが、GPU 上の最近使用済みコンテンツ用16MB キャッシュを活用。
- WebAssembly から GPU への渡航回数を削減し、データ転送量を約60% 減少。
- 高解像度での安定化:
- ネイティブ解像度の 3 倍ではフレームレートが低下するが、シェーダーの分析により「分岐(Divergent)によるセットアップ遅延」を解消。
- セットアップを統一し、各フレームデータを 1 枚分の処理として扱うことで、統合 GPU でも毎秒 48〜50 フレームへ向上。
- ブラウザスケジューリング最適化:
- ネストした
の最大 4ms クランプ問題を解決するため、ループをsetTimeout
を通じてMessageChannel
(非同期実行)に変更。_yield_ - 処理をワーカー側に移し、ページスレッドは GPU 供給のみを行うことで、レースのフレームレートを安定して毎秒 50 フレームに保持。
- ネストした
「ゴッド・オブ・ウォー II」で発見された問題点
GT4 では検出されなかったバグやギャップが見つかりました。
- 巨大な多角形とクラッシュ:
- インтроシーン後にレンダリングリストがメモリを超え、ゲームクラッシュする挙動が発生。
- 原因: リコンパイルャーの挙動。16KiB 単位内の最後の分岐命令で、確実なパスの実行順序(遅延スロット vs 分岐後)が間違っていたため、破損したリンクリストを辿りクラッシュ。
- 解決: このバグ修正により戦闘シーンまで到達可能に。
- 残存三角形:
- 画面の一部に巨大な多角形が表示される問題は、本来除去すべき三角形が削除されていないため。
- クリップフラグのタイミング:
- 「ゴッド・オブ・ウォー II」のマイクロコードは CLIP 命令から 3 つ命令後に読み取りますが、モデルでは 4 つ命令後と設定していた差異を「GT4 のような長い待機時間がない限り検出されなかった」ことが判明。
- 追加機能:
- BIOS が提供する起動パス(メモリロード)、スクラッチパッド DMA の宛先チェーンモードなどが必要。
ご自身のゲームを移植する方法
必要な環境
,git
,CMakeNinja- C++20 対応コンパイラ(clang推奨、自動検出あり)
- Python 3
- .7z フォーマットの場合:7-Zip
- プラットフォーム: x86_64 アーキテクチャの Linux で動作確認済み
インストール手順
- リポジトリをクローン。
に Emscripten SDK をインストールするか、指示されたものを使用。tools/emsdk- ネイティブランナー用の
をダウンロード。wgpu-native
git clone https://github.com/snuri00/ps2-web-recomp.git cd ps2-web-recomp scripts/port.sh mygame "My Game.iso" python3 scripts/serve.py build/web-mygame games/mygame/disc.iso # http://localhost:8620/
port.sh
と自動実行フロー
port.sh
はディスクイメージ(ISO や ZIP、7z)を受け取り、以下の処理を自動実行。port.sh- 起動プログラムを読み込み、マシンコードを C++ に翻訳。
- ヘッドレス状態で数回ゲームを実行し、到達できないアドレスを書き留める。
- それらをエントリーポイントとして追加し、再度翻訳する(ループ)。
- ゲームがアップロードするベクトルユニットマイクロプログラムを記録・コンパイル。
でページビルド、「build/native-mygame」でネイティブランナービルド。build/web-mygame
- 処理時間: 8 コアノートパソコンで ISO から Web ページへの変換に約3 分半。
探索実行オプション
- スタートボタン押下後、5 秒ごとに進めてタイトル画面を通過するよう設定。
- スロット 1 に空のメモリアルカードを挿入(ページが採用する形式)。
- --press 900:cross: 自動操作を停止し、ユーザーの入力に切り替える。
- --frames: 各実行の継続時間を設定可能。
結果の確認
- 最後のフレームデータは
に保存される。ここを確認して新しいゲームの到達範囲をチェックできる。games/mygame/shots - 既存の Web ページを再ビルドするには、
を使用。scripts/build_web.sh mygame
制限事項
- 現在確認済みのタイトルは 2 つのみ。
- 次のタイトルでは、未実装のシステムコールや IOP ライブラリ関数で停止する可能性があります(ログ例:
,[sys] unhandled syscall
)。[iop] missing <library> - レンダラーがまだ対応していない GS 機能を使用すると動作しない場合があります。
ブラウザ環境について
WebGPU の必要性と設定
- WebGPU 必須: GPU レンダリングのため、Chrome や Edge(Windows/macOS)での利用が可能。
- Linux 上の設定:
- デフォルトでは WebGPU が無効化されている場合があるため、以下のフラグをオンしてブラウザ再起動が必要。
chrome://flags/#enable-unsafe-webgpu chrome://flags/#enable-vulkan- アダプターが見つからない場合は、ソフトウェアレンダラーにフォールバック(メニュー表示は OK だが 3D 動作は非常に遅い)。
複数 GPU の場合
- 2 GPU搭載のノートパソコンなどで、離散型 GPU(dGPU)でのキャンバス描画が表示されない場合があります。
- ページは両方のアダプターを試み、失敗すれば 2D キャンバスへのコピー処理を行います。
キーボードマッピング
| PS2 ボタン | キーボードキー |
|---|---|
| ファーストボタン (X) | Z |
| サークルボタン (○) | X |
| スクエアボタン (□) | A |
| トライアングルボタン (△) | S |
| ショルダー (L1/R1) | Q, W |
| ショルダー (L2/R2) | 1, 2 |
| スタート | Enter |
| セレクト | Backspace |
| D パッド | 矢印キー |
| リフトスティック | I, J, K, L |
ゲームパッド対応
- ブラウザの Gamepad API を使用。
- アナログトリガーは DualShock 2 の L2/R2 としてマッピング。
- 振動機能:Chrome/Edge 等に対応するブラウザでは、ゲームから要求を受けるとコントローラーが振動。
- スマートフォン: オンスクリーンコントロールが表示され、F キーでフルスクリーンモードの切り替えが可能。
- 解像度選択器: 新しいサイズでページを再読み込みします。
ネイティブ実行(コンソール以外での動作)
SDL2 がインストールされている場合、
build/native-mygame/ps2run を使用してウィンドウモードやフルスクリーンで動作可能です。
build/native-mygame/ps2run games/mygame/disc/SLUS_123.45 --disc games/mygame/disc.iso --window --memcard mygame.ps2
オプションと設定
- --native-only: ブラウザビルドをスキップし、ネイティブビルドのみを実行。
- フルスクリーン切り替え:
オプションまたはF11 キー。--fullscreen - 高解像度モード: 環境変数で設定可能。
export PS2_SCALE=2 # または 3 - スレッド制御:
- デフォルト:エミュレーション、ベクトルユニット 1、GS はそれぞれ独立したスレッド動作(「ゴッド・オブ・ウォー II」ではネイティブ解像度で43 fps → 60 fpsへ改善)。
- PS2_ONE_THREAD=1: 設定すると単一スレッドに強制変更。
- メモリアルカード: ページのエクスポート形式と同じレイアウトを使用。
- Steam Deck 対応: Steam のライブラリとして追加可能ですが、実機での動作確認事例は未だありません。
セーブデータの保存と移動
ページはスロット 1 にメモリアルカードを置き、プレイの進行状況ブラウザストレージに保持します(リロード後も進行可)。
- エクスポート: PCSX2 の形式(
)としてダウンロード可能。.ps2 - インポート: PCSX2 との間やブラウザ間のデータ移動が可能。
- --nocard: オプションを指定すると、スロットを空のまま開始できます。
ホスト環境と法的注意事項
サーバーサイド要件
- ページは SharedArrayBuffer を使用するため、以下のヘッダー付与が必要(クロスオリジンアイソライズド)。
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
はこれらのヘッダーを送信し、ディスクストリーミング用の Range リクエストもサポート。scripts/serve.py
知的財産とライセンス
- コード: オリジナルおよび独自のライセンス下にあるサードパーティのコードのみを含む。ゲームデータは一切含まない。
- 生成物: 作成された C++ コードと WebAssembly は、元のゲーム実行ファイルからの翻訳であり、ゲームの所有者に帰属。
- 配布禁止: ご自身のマシンでのみ保持し、公開しないでください。ご自身がお持ちのディスクイメージの使用に限ります。
- 商標免責: 「グランツーリスモ」「ゴッド・オブ・ウォー」「PlayStation」はソニー関連企業の商標です。本プロジェクトは Sony や開発スタジオとは無関係であり、何らの endorsements も受けていません。
クレジットとライセンス情報
- MPEG デコーダー: VLC テーブルを Play! (BSD 2-clause) から適応。
- 参考資料: PCSX2、Play!、ps2sdk のヘッダー。
- WebGPU/ビルド: wgpu-native、Dawn、Emscripten。
詳細は
LICENSE ファイルおよび runtime/src/ipu/NOTICE をご参照ください。