├── README.md ├── emugen ├── Netplay.md ├── README.md ├── Recording_Video.md ├── RetroArch.md ├── Texture_filtering.md ├── Vsync.md └── images │ └── Nearest_Neighbor_2x_vs_HQ2x.png ├── emulation-accuracy.md ├── hqx-sharder ├── README.md ├── algorithm.md └── sample │ ├── LICENSE │ └── README.md ├── ips_file_format.md ├── libretro ├── README.md ├── cores │ ├── developing-cores.md │ └── implementing-the-api.md ├── libretro-overview.md ├── misc │ └── core_info.md └── retroarch │ └── netplay.md ├── lle-vs-hle.md ├── near ├── README.md ├── advice │ └── shoulders-of-giants.md ├── audio │ └── dynamic-rate-control.md ├── cpu │ └── alu.md ├── design │ ├── cooperative-serialization.md │ ├── cooperative-threading.md │ ├── hierarchy │ │ ├── README.md │ │ ├── bsnes_v112.png │ │ ├── exertainment-bike.jpeg │ │ ├── higan_v105.png │ │ ├── higan_v107.png │ │ ├── micro-machines-2.jpeg │ │ ├── msx.jpeg │ │ ├── sufami-turbo.jpeg │ │ └── super-multitap.jpeg │ └── schedulers.md ├── input │ ├── latency.md │ └── run-ahead │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── README.md └── video │ └── color-emulation │ ├── 1_gamma.png │ ├── 1_original.png │ ├── 2_gamma.png │ ├── 2_original.png │ ├── README.md │ ├── gba_lcd_gamma.png │ ├── gba_lcd_original.png │ ├── gbc_lcd_gamma.png │ ├── gbc_lcd_original.png │ ├── grayblack.png │ └── graywhite.png ├── others ├── GBZ80Opcodes.pdf ├── SM83_decoding.pdf ├── ThumbRefV2-beta.pdf ├── armref.pdf ├── netplay.md └── w65c816s.pdf └── preparing-your-game-for-deterministic-netcode.md /README.md: -------------------------------------------------------------------------------- 1 | # emu-docs-ja 2 | 3 | ## コンテンツ一覧 4 | 5 | - [翻訳: Near's Respite](near/README.md) 6 | - [翻訳: Libretro Docs](libretro/) 7 | - [翻訳: Emulation General Wiki](emugen/) 8 | - [翻訳: HQx-shader](hqx-sharder/) 9 | - [エミュレータの精度と速度について](emulation-accuracy.md) 10 | - [LLE vs HLE](lle-vs-hle.md) 11 | - [IPSファイル](ips_file_format.md) 12 | - [決定論的ネットコード](preparing-your-game-for-deterministic-netcode.md) 13 | - [その他・未分類のデータ](others/) 14 | 15 | ### コンソール 16 | 17 | - [GameBoy](https://github.com/akatsuki105/gb-docs-ja) 18 | - [GameBoyAdvance](https://github.com/akatsuki105/gba-docs-ja) 19 | - [Nintendo DS](https://github.com/akatsuki105/nds-docs-ja) 20 | - [Super Famicom](https://github.com/akatsuki105/snes-docs-ja) 21 | -------------------------------------------------------------------------------- /emugen/Netplay.md: -------------------------------------------------------------------------------- 1 | ## Netplay 2 | 3 | その前に、いくつかの注意点をご紹介します。 4 | 5 | - エミュレータのネット通信は完璧ではありません。頻繁に同期ずれが発生します。また、ゲームプレイが途切れたり、乱れたりすることもよくありますし、コントローラーの入力ラグが発生することもあります。これは経験に伴うものです。 6 | - マルチプレイを楽しむのに十分な同期状態を保つには、何度か連続して試す必要があります。落ち込まずに、うまくいくまでもう一度試してみてください。 7 | - Netplayを使用する際は、ダウンロード中のもの(Torrent、YouTubeの動画など)がないようにしてください。通信が不安定になります。 8 | - エミュレータのウィンドウを移動したり、チャットウィンドウ、Kailleraウィンドウ、エミュレータウィンドウ以外のウィンドウにフォーカスを移さないようにしてください、そうするとゲームが同期ずれを起こしてしまいます。 9 | - コントローラーを使用することを強くお勧めします。一部のエミュレータでは、別のチャットウィンドウでチャットをしていても、キーボードからの入力がキャッチされることがあります。 10 | 11 | ### ポートフォワーディング 12 | 13 | エミュレータやクライアントの中には、ポートを転送する必要があるものもあるので注意が必要です。その場合は、[Port Forwarding Guide For Netplay](https://emulation.gametechwiki.com/index.php/Port_Forwarding_Guide_For_Netplay)に従ってください。 14 | 15 | ## ローカルマルチプレイ 16 | 17 | ここでは、もともとローカルマルチプレイ(カウチマルチプレイ)と呼ばれる、複数のコントローラーを1台のゲーム機に接続してプレイできるゲーム機を取り上げます。 18 | 19 | また、利便性の観点から、同じ部屋の中で、2台のゲーム機の間のわずかな距離でリンクケーブルや無線を必要とする携帯ゲーム機も、ローカルマルチプレイとみなします。 20 | 21 | ネットプレイは、ローカルマルチプレイをエミュレートするもので、世界中のプレイヤーが同じ部屋にいるかのように同じゲームをプレイすることができます。 22 | 23 | ### Remote Access alternatives 24 | 25 | #### Parsec 26 | 27 | Parsecはリモートアクセスです。 28 | 29 | Windows PCがあれば、世界中のあらゆるマルチプレイヤーゲームをParsecでオンラインプレイすることができます。詳しくは https://parsec.app/local-co-op-online をご覧ください。 30 | 31 | #### Steam Remote Play Together 32 | 33 | Steamのローカルマルチプレイヤーゲームなら、SteamのRemote Play Togetherを使用してインターネット経由で友人と瞬時に無料で共有できます。 34 | -------------------------------------------------------------------------------- /emugen/README.md: -------------------------------------------------------------------------------- 1 | # Emulation General Wiki 2 | 3 | https://emulation.gametechwiki.com/index.php 4 | 5 | ## 🖲 Not really emulators 6 | 7 | ### Frontends 8 | 9 | - [RetroArch](RetroArch.md) 10 | 11 | ## ❓ FAQs 12 | 13 | - [Netplay](Netplay.md) 14 | - [VSync](Vsync.md) 15 | - [ビデオ録画](Recording_Video.md) 16 | 17 | ## 🪅 Shaders and filters 18 | 19 | - [テクスチャフィルタリング](Texture_filtering.md) 20 | 21 | -------------------------------------------------------------------------------- /emugen/Recording_Video.md: -------------------------------------------------------------------------------- 1 | # ビデオ録画 2 | 3 | ## 3rdパーティの録画ソフトウェア 4 | 5 | 多くの場合、このようなプログラムを使ってゲームプレイを録画するのが一番良いし、簡単だと思います。 6 | 7 | - Bandicam (must use version 1.8.2 or higher if you want lossless recording via third-party VFW codecs) 8 | - DxTory (¥3600) 9 | - Fraps 10 | - MSI Afterburner (freeware) 11 | - Open Broadcaster Software (Local Recording mode) (free software) 12 | - なお、OBSでは、ロスレス映像を生成する設定やロスレスコーデックを使用しても、ロスレス映像を得ることはできません。録画された映像は、処理される前からロスが発生しています。 13 | - ShadowPlay (Nvidia exclusive) (freeware) 14 | - CamStudio (Open Source) 15 | - Camtasia 16 | 17 | ## General Recording 18 | 19 | Snes9x や Nestopia などのエミュレータでは、Video for Windows(VFW)フレームワークを利用して、映像を録画する際に特定のビデオコーデックを選択できるようになっています。 20 | 21 | オプションには、Lagarithのようなロスレスコーデックで録画するものもあり、非圧縮の場合よりもファイルサイズを大幅に削減することができます。 22 | 23 | これらのエミュレータは、オーディオを非圧縮PCMで記録し、記録が終わったら両方のストリームをAVIファイルにミックスします。 24 | 25 | ## RetroArch 26 | 27 | RetroArchはロスレスのRGB x264とFLACオーディオで録画しますが、高解像度のビデオの場合はディスクの容量や処理能力をかなり消費します。 28 | 29 | 録画オプションは現在RGUIには実装されていませんので、Phoenixを使うかCLIを使う必要があります。MKV形式での録画をお勧めします。 30 | 31 | コマンドラインで次のように入力してください。 32 | 33 | ```sh 34 | > retroarch.exe -r "C:\Path_to_Recordings\recording_name.mkv" --menu 35 | ``` 36 | 37 | デフォルトでは、RAはネイティブな解像度で録画しますが、より高い解像度で録画するには、`--size`を使用し、`WIDTHxHEIGHT`を指定します。また、`--recordconfig`を設定することで、録画時に特定の設定ファイルを使用することができます。デフォルトでは、フィルターのみを記録します。 38 | 39 | シェーダーを使って録画するには、設定ファイルに `video_gpu_record = true` を追加してください。解像度やシェーダーによっては、パフォーマンスが大幅に低下することがあります。([動画例](https://youtu.be/jflrB0UIcFE)) 40 | 41 | ## PCSX2 42 | 43 | PCSX2でビデオを録画するには、GSdx videoプラグインを使用している状態で、ゲームプレイ中にF12を押すだけです。 44 | 45 | 設定可能なウィンドウが表示されます。オーディオについては、SPU2-X の設定で `Enable Debug Options` をチェックし、`Debugging options` で `Dump Memory Contents` をチェックします。 46 | 47 | ## Dolphin 48 | 49 | かつてはDolphin経由での録画は推奨されていませんでした。A/Vシンク用に編集された特定のビルドが必要になり、Dual CoreやIdle Skipping(同期ずれの原因)が使えなかったからです。 50 | 51 | これらの問題は解決されており、Dolphinは2017年からffmpegのダンプを内蔵しています。 52 | 53 | また、ロスレスでダンプしたい場合は、グラフィックス設定ダイアログボックスの最後のタブにある`Frame Dumps use FFV1`にチェックを入れることをお勧めします。 54 | 55 | -------------------------------------------------------------------------------- /emugen/RetroArch.md: -------------------------------------------------------------------------------- 1 | # RetroArch 2 | 3 | RetroArch(旧SSNES)は、CまたはC++で書かれた、オープンソースでマルチプラットフォームのlibretroフロントエンドです。高速、軽量、移植性に優れた設計になっています。 4 | 5 | ## Features 6 | 7 | - 複数のプラットフォームで一貫性があり、同じUI構造と完全な機能を持つコマンドラインインターフェースを使用しています。 8 | - ゲームパッドで操作可能なメニューシステムで、XMBやMaterial UIなど複数のスタイルが用意されています。 9 | - ゲームパッドの自動設定プロファイルにより、接続時にゲームパッドが自動的にマッピングされます。XInputコントローラの自動設定は、アプリケーションに組み込まれており、他のタイプのコントローラは外部プロファイルとして利用できます。 10 | - コアごと、ゲームごとの設定をオーバーライド可能です。 11 | - ビデオ出力の解像度とリフレッシュレートをカスタマイズでき、専用のフルスクリーンモードとマルチモニター設定用のモニターインデックスを備えています。 12 | - DRCにより、ゲームの出力レートがシステムと異なる場合でも、音声と映像をスムーズに再生できます。GBAのように60Hzに対応していないシステムでは特に顕著です。 13 | - 基本的な早送り、SRAMセーブ、セーブステートなどが実装されています。リアルタイムの巻き戻しやネットプレイに使用されるエミュレーション状態のシリアライズをサポートしています。 14 | - 主にCgとGLSLを使用したピクセルシェーダーで、Xbox 360ではHLSLを使用しています。使用しているシェーダーフォーマットは柔軟で、かなり使いやすく、ランタイムパラメーターを調整して複雑なマルチパスエフェクトを実現することができます。また、ソフトウェアで実行される従来のビデオフィルタプラグインにも対応しています。 15 | - .dsp設定ファイルによるオーディオDSPフィルタープラグインに対応。 16 | - カスタムオーバーレイ対応。 17 | - FFmpegの記録と再生に対応。コアのネイティブ解像度の出力か、フロントエンドのポストプロセスされた出力のどちらかを記録することができます。再生は、内部のFFmpeg libretroコアを介して処理されます。 18 | - ストリーミング対応。RetroArchにTwitchアカウントを追加し、ちょっとした設定をすることで、プレイしたゲームの内容をTwitchで配信することができます。 19 | - GGPOのようなネットプレイ(レイテンシ隠しのロールバック搭載)。参加者全員がフルスピードでエミュレートし、地理的に遠いところ(世界の裏側くらい)に住んでおらず、適切なインターネット速度を持っていれば、ラグを感じないプレイが可能になるはずです。P2PのUDPを使用し、2人プレイをサポートします。ロールバックのため、動作にはかなりのCPUパワーが必要で、シリアル化をサポートするコアでのみ動作します。 20 | - ビデオドライバによるバッファリングを排除することで、vsyncに関連する入力レイテンシーを低減するオプションや、フレームディレイを使用して、ディスプレイ上でvsyncが発生する直前まで入力のポーリングを遅延させるオプションがあります。 21 | - レイテンシを減らすために先取り実行を備えています。この設定では、サイクル精度の高いエミュレータが、必須の1~2フレームの入力ラグを先取りして実行します。これにより、エミュレータはオリジナルのハードウェアよりも優れたレイテンシを達成することができます。 22 | 23 | ## Netplay 24 | 25 | Netplayは、現在のビルドでは、「Settings > Netplay」オプションでメニューから使用できるようになりました。古いビルドでは、コマンドラインや[RetroArch-Phoenix Launcher](https://www.mediafire.com/download/yrydc78bl6y82z4/retroarch-phoenix.7z)でも動作するようになっています。 26 | 27 | ゲームをホスト(サーバー)するのか、参加(クライアント)するのかを指定する必要があります。参加する場合は、下のフィールドにホストのIPアドレスを入力する必要があります。ファイアウォールでポート55435が開かれていること(デフォルト、変更可能)、ルーターでポートが転送されていること(該当する場合)を確認してください。また、「スペクテイター・モード」を指定すると、任意の数の観客が参加でき、自分ではプレイできずにあなたのプレイを見ることができます。 28 | 29 | 遅延フレームとは、実際のネットワーク遅延のために、RetroArchのlibretroコアが同期を維持するために一度にエミュレートする必要のあるフレーム数の最大値です。この数は、相手にpingを打って、その時間(ミリ秒)を16で割ると、だいたいの目安になります(だいたい60fpsのゲームの1フレームのミリ秒数です)。ゲームプレイが少しぎこちない場合は、遅延フレームの数を少し増やしてみてください。 30 | 31 | GGPOプラットフォームと同様に、RetroArchでは、ボタンを押したときに発生するセーブステートの一定の流れを、サーバーとクライアントのマシン間で交換・比較します。セーブステートが乖離し始めると、ゲームは両者が一致するところまで時間を巻き戻し、その後、libretroコアが不足しているフレームを一斉にエミュレートして適切な場所に戻します。これにより、完全にラグのない入力をしているかのような錯覚に陥り、ヒネリの効いた細かい操作には非常に有効です。 32 | 33 | サーバーに接続しようとすると、すぐに`client disconnected`と表示される場合は、ログを開いてROMが完全に一致していることを確認してください(一致していない場合は、ハッシュの不一致を訴えます)。 34 | 35 | 奇妙なタイムアウトエラーが出た場合は、ウィンドウを閉じてからもう一度接続してみると、自然に解決するはずです(ネットワーク遅延の過度の急増により、状態が壊滅的に乖離し、このエラーが発生することがあります)。 36 | -------------------------------------------------------------------------------- /emugen/Texture_filtering.md: -------------------------------------------------------------------------------- 1 | # テクスチャフィルタリング 2 | 3 | テクスチャフィルタリングとは、3Dモデルのテクスチャーを滑らかにするための手法のことです。 4 | 5 | テクスチャフィルタリングは、NINTENDO64以降のすべての家庭用ゲーム機に搭載されているほか、最近のPCやAndroid端末にも搭載されています。 6 | 7 | そのため、エミュレーションの際にも便利で、様々なエミュレータで対応することにより、もともと搭載されていないゲーム機のエミュレータにも適用することができます。 8 | 9 | ![](./images/Nearest_Neighbor_2x_vs_HQ2x.png) 10 | 11 | ## テクスチャフィルタリング の種類 12 | 13 | 種類 | 日本語名 | 詳細 | Pros | Cons 14 | -- | -- | -- | -- | -- 15 | Nearest neighbor | 最近傍法 | この方法では、指定した解像度で画像を表示するために、ピクセルを元の解像度での配置に関連する最も近い場所にソートします。 | 高速。整数値(例えば、正確に2倍または4倍の解像度)では、実質的に「フィルタリングなし」です。 | フィルタリングされていないピクセルは、あるものは他のものよりも太く、奇妙に見える傾向があります。その結果、ピクセルアートが鮮明でなくなったり、テキストが読みづらくなったりします。 16 | Bilinear | バイリニア | この線形フィルタリング法は、近傍テクスチャの画素の色データを使用し、複数ビットの色データを組み合わせて、一部の画素を平均化した色に置き換えることで、色が急に変わるのではなく、徐々に切り替わるようにしています。 | 3DゲームはNearest Neighborより良い結果が得られることが多いです。テクスチャのフィルタリング/スケーリングの中では、最もシステム負荷の少ない方法です。 | 低解像度では、2Dゲームが非常にぼやけやすくなります。これは特に2Dゲームや低解像度の3Dゲームで顕著です。ハードウェアがあれば、より複雑なフィルタリング方法で透明度を保つこともできます。 17 | Trilinear | トリリニア | この線形フィルタリング法は、Bilinearフィルタリングと同じことを行いますが、2回通過することで、より滑らかなグラデーションを得ることができます。 | 3Dゲームではバイリニアフィルタリングよりも良さそうです。 | バイリニアと同様に、低解像度のゲームでは、この方法ではぼやけて見えることが多いです。 18 | HQx | -- | テクスチャスケーリングアルゴリズム。テクスチャの直近のバージョンをスケールアップし、ギャップをそのギャップの隣のピクセルのコピーで埋めます。 | 本質的には破壊的なフィルターですが、ゲームによっては(例:Yoshi's Island)、このフィルターを使うことで、アニメのような外観を維持することができます。 | Artifactがよく起きます。45°以外のカーブやスロープは、他のものと比べてギザギザに見えます。テクスチャやスプライトの細かい部分は、エッジ検出が悪いと見えなくなってしまうことがあります。ポスタリゼーションは非常によく起きますが、しかし、いくつかのエミュレータ(PPSSPPなど)には、この問題を解決するためのデポスタリゼーションフィルタがあります。xBRが得意とするものをより苦手とします。 19 | 2xSaI | -- | テクスチャースケーリングアルゴリズムです。テクスチャをスケーリングし、ソースから取得したピクセルとランダムに推測した色の混合物でエッジを埋めます。 | HQxやxBRよりもシステム負荷が低く、利用可能な最良のオプションであれば十分です。 | すでに古いアルゴリズムなので別のアルゴリズムを使ってください。エッジ検出は大の苦手です。 ArtifactとPosterizationがよく起きます。 20 | xBR | -- | HQxの改良版です。エッジの検出が良くなり、曲線や45度以上/以下の傾斜の場合に効果があります。 | 2Dゲームが得意です。エッジ検出機能がHQxよりも向上しています。これにより、テクスチャのカーブやスロープがより滑らかになり、アーチファクトも少なくなりました。3Dゲームもきれいに見えます。 | 省略 21 | xBRZ | -- | xBRを改良したもので、10ピクセル以下の小さなフィーチャーのスケールアップに優れている点を除けば、非常によく似ています。 | 2Dゲームが得意です。HQxやxBRで台無しになってしまう小さな特徴を検出します。3Dゲームもきれいに見えますよ。 | 省略 22 | 23 | ## Durante's Hybrid and Deposterization Filters for PPSSPP 24 | 25 | PPSSPPには「ハイブリッド」と呼ばれるスケーリングオプションがあります。また、Deposterizeというオプションもあります。 26 | 27 | ポスタリゼーションとは、あるピクセルから別のピクセルへの色相の急激なコントラストを意味し(低品質のGIFでは非常によく見られます)、長い間、テクスチャースケーリングアルゴリズムを悩ませてきた問題です。 28 | 29 | Durante氏のフィルタは、テクスチャ情報に応じて、xBRフィルタリングとバイリニア/バイキュービックフィルタリングを切り替える。 30 | 31 | その上、Deposterizeオプションは、圧縮されたテクスチャのポスタリゼーションエッジに取り組み、シャープな移行ではなく、滑らかなグラデーションを可能にしています。 32 | 33 | 完璧ではありませんが(完璧なスケーラーは、残念ながら現在のコンピューティングパワーでは不可能です)、それでも素晴らしいものであり、スペックがあればPPSSPPに使用することをお勧めします。 34 | 35 | -------------------------------------------------------------------------------- /emugen/Vsync.md: -------------------------------------------------------------------------------- 1 | # Vsync 2 | 3 | Vsync(Vertical Synchronizationの略)は、ゲームのビデオ出力をディスプレイのリフレッシュレートに同期させ、画面のティアリングを防ぐために使われます。垂直同期とも言われます。 4 | 5 | エミュレータでのVsyncはこれまで問題が多く、オーディオドライバのバッファリングによる遅延の問題や、ゲームがエミュレータと異なるリフレッシュレートで動作することによるスタッタリングが発生していました。 6 | 7 | また、オーディオに同期するエミュレータでは、タイミングの不完全さによってオーディオバッファが溢れるまたは枯渇すると、Vsyncのスタッタリングが発生することがあります。 8 | 9 | 固定フレームレートのエミュレータでVsyncのパフォーマンスを向上させるいくつかの方法を以下に示します。 10 | 11 | > ティアリング: 1枚の画像の中に複数フレームの画像が描画されてしまい、映像が途中で左右にずれたように見える現象のことです。 12 | 13 | > スタッタリング: 俗に言う「カクツキ」のことです。映像が途中でカクッカクッと一瞬止まったようになる現象です。 14 | 15 | ## SRC - Static Rate Control 16 | 17 | このオーディオ同期方法は、起動時にエミュレートされたシステムのオーディオ入力レートを、ホストシステムの出力レートに合わせて、一定の制限内で調整します。 18 | 19 | これにより、ゲームのビデオのリフレッシュレートが現在のディスプレイのリフレッシュレートと同じでなくても、Vsyncはスムーズに動作します。 20 | 21 | 十分に小さな調整であれば、オーディオのピッチやスピードの違いが目立つことはありませんが、大きな調整では目立ってしまうので、通常はオーディオレートの変化を最大5%までに抑えるようにしています(60Hzのディスプレイであれば、57Hz~63HzのゲームをスムーズにVsyncすることができます)。 22 | 23 | 調整はエミュレータの起動後には変更されないため、システムのフレームタイミングのわずかなずれがVsyncのスタッタリングを引き起こす可能性があります。 24 | 25 | ただし、システムのタイミングを正確に測定することができなければ、多くの場合、この調整は非常に困難または不可能です。 26 | 27 | 可変フレームレートを使用するエミュレーターではエミュレートされたディスプレイで50FPSまたは60FPSに固定されていない場合、うまく動作しないことがあります。 28 | 29 | この方法は、higanやRetroArchで実装されていることが知られています。 30 | 31 | ## DRC - Dynamic Rate Control 32 | 33 | この方法は、オーディオ入力レートをオンザフライで再調整し、タイミングの不一致によるオーディオバッファの溢れや枯渇を防ぎ、Vsyncのスムーズな動作を維持します。 34 | 35 | 調整量はダイナミックレートコントロールデルタによって決定されますが、デフォルトでは人間の耳には聞こえないと思われる0.5%に設定されています。 36 | 37 | 起動時のSRCと組み合わせることで、極めて正確なシステムのタイミング情報を必要とせずに、エミュレーションをディスプレイにほぼ完全に同期させることができます。 38 | 39 | この方法は、nemulatorやRetroArchに実装されていることが知られています。 40 | 41 | ## Hard GPU Sync 42 | 43 | この方法は、ビデオドライバのVsyncバッファリングに伴うレイテンシを軽減するためのものです。 44 | 45 | ベンダは一般的に、ベンチマークでのスコア向上やPCゲームでのパフォーマンス向上のために、ビデオドライバにバッファリングを実装していますが、これはレイテンシに悪影響を及ぼし、特にこのレイテンシを考慮して作られていない古いゲームのエミュレーションには悪影響を及ぼします。 46 | 47 | Hard GPU Syncは、OpenGLの拡張機能である`ARB_sync`を使って、指定したフレーム数だけバッファリングを行うようにドライバを強制し、レイテンシを改善する可能性があります。 48 | 49 | 0フレームのバッファリングはパフォーマンスにかなりの負担がかかり、フレームドロップを避けるためにエミュレータを60fpsのフルスピードで動作させるのに必要な速度の何倍もの速度で動作させる必要があります。 50 | 51 | 1フレーム以上のバッファリングは、パフォーマンスの要求を軽減します。Linux上のDRM/KMSも同様の方法で利用することができますが、ビデオバッファを直接制御することにより、パフォーマンスが大幅に向上します。 52 | 53 | この方法は、RetroArchにしか実装されていません。 54 | 55 | ## G-sync と FreeSync 56 | 57 | これらの方式は、Nvidia社とAMD社がそれぞれ開発しているVsyncの代替方式です。 58 | 59 | これらに対応した新しいディスプレイが必要で、ゲームがディスプレイに同期するのではなく、ディスプレイがゲームに同期するフレームレートを可変に調整することができます。 60 | 61 | MAMEでは、この機能を利用して、アーケードゲームをネイティブのリフレッシュレートで動作させることができ、スタッタリングやオーディオのピッチやスピードの違いもありません。 62 | 63 | RetroArchでは、設定で`Sync to Exact Content Framerate`をオンに設定してSRCとDRCを無効にすると、この機能を利用することができます。(この機能を使用するには、RetroArchでVsyncがオンになっている必要があります) 64 | 65 | -------------------------------------------------------------------------------- /emugen/images/Nearest_Neighbor_2x_vs_HQ2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/emugen/images/Nearest_Neighbor_2x_vs_HQ2x.png -------------------------------------------------------------------------------- /emulation-accuracy.md: -------------------------------------------------------------------------------- 1 | # エミュレータの精度と速度について 2 | 3 | >**Note** 4 | > この記事は [Emulation Accuracy, Speed, and Optimization](https://mgba.io/2017/04/30/emulation-accuracy/) を翻訳したもので、主にエミュレータ開発者向けです。 5 | 6 | --- 7 | 8 | エミュレーションでよく話題になるのが、エミュレータの「精度」です。 9 | 10 | この言葉は、エミュレーションがオリジナルのハードウェアの動作にどれだけ近いかを意味します。 11 | 12 | しかし、この単純な言葉の裏には、かなりの複雑さが隠されています。 13 | 14 | 何をもってエミュレータの「精度」とするか、単純な指標はありません。 15 | 16 | 精度の高いエミュレータとは、(速度は遅くても、)バグが少ないことを意味すると考えられています。精度の低いエミュレータは、ほとんどの場合、高速に動作し大半のゲームを遊ぶのに十分な場合が多いです。 17 | 18 | これらの主張には核心をついていますが、現実にはもっと多くのことがあります。 19 | 20 | --- 21 | 22 | エミュレーションの精度を表す言葉としてよく使われるのが`サイクル精度(cycle accuracy)`です。 23 | 24 | この言葉は、しばしば誤解され広義に適用されています。 25 | 26 | サイクル精度とは、本来は、エミュレートされたシステムのすべての構成要素(CPU, GPU, メモリバスなど)が、他のすべての構成要素と比較して正しいタイミング(つまり同期して)で動作することを意味します。 27 | 28 | タイミングが厳しく、ハードウェアへのアクセスがより直接的な多くのシステム、特に古いシステムでは、サイクル精度は高精度なエミュレーションの重要な要素です。 29 | 30 | --- 31 | 32 | サイクル精度という言葉の`サイクル`とは、デジタルロジックのタイミングの基本単位である「クロックサイクル」のことです。 33 | 34 | 一般的にシステムやプロセッサを表すMHzやGHzという数値は、そのシステムのクロックの周波数を意味しています。 35 | 36 | ゲームボーイアドバンスのように16MHzのプロセッサを搭載したシステムでは、1秒間に1,600万回の動作をしていることになります。 37 | 38 | もう一つの重要なハードウェアであるバスは、プロセッサとは異なるクロックレートを持っている場合もあります。 39 | 40 | バスとは、CPUとメインメモリの間など、システムのさまざまなコンポーネント間でデータを転送する相互接続のことです。 41 | 42 | ニンテンドーDSでは、ARM7(GBAと同じCPU)とARM9という2つのプロセッサがあり、それぞれが異なるクロックスピード(約33MHzと約67MHz)で動作していますが、バスはARM7と同じ33MHzで動作しています。 43 | 44 | そのため、ARM9のクロックと同じ速度のバスであれば素早く取得できるデータを、メモリから取得するために、ARM9は自身より遅いバスで待たなければならないことがあります。 45 | 46 | --- 47 | 48 | `サイクルカウント精度(cycle-count accuracy)`も同様の概念ですが、すべての構成要素が、他の構成要素に対して正しいサイクルで(つまり同期して)エミュレートされるのではなく、各コンポーネントはアトミックに動作し、正しい時間を要しますが、他の構成要素のタイミングと正しく同期が取れていない場合があります。 49 | 50 | このように、サイクルカウント精度はサイクル精度に比べて厳密に劣っているように聞こえるかもしれませんし、ハードウェアの完全な精度という観点からはその通りです。 51 | 52 | しかし、サイクルカウント精度は、エミュレーションの設計、実装、保守が非常に容易なスタイルです。 53 | 54 | mGBAがサイクル精度の観点から正確になっている、またはなるだろうというのはよくある誤解ですが、そうするためにはmGBAの基礎的な要素のいくつかを大きく書き換える必要があります。 55 | 56 | うまく実装されていれば、サイクルカウント精度の高いエミュレータは、サイクル精度の高いエミュレータと非常に似通った、あるいはしばしば同一の結果をもたらします。 57 | 58 | --- 59 | 60 | サイクル精度が解決する主な問題は、同じサイクルでアクションを実行する異なるハードウェアを正しくエミュレートすることです。 61 | 62 | これは、あるサイクルで発生する個々のステップをすべて順番に実行し、次のサイクルに進み、それを繰り返すという簡単な作業のように聞こえるかもしれません。 63 | 64 | これは非常に時間がかかるものであり、サイクル精度重視の設計とサイクルカウント精度重視の設計との間にパフォーマンスの大きな違いをもたらします。 65 | 66 | ハードウェアは、これらすべてのステップを任意のサイクルで独立して実行します。ソフトウェアはこれらのステップを並行して実行することができないため、演算の間でスワップを行う必要があります。 67 | 68 | これは計算量が多く、結果的にエミュレータの実行は遅くなります。例を見てみましょう。 69 | 70 | ![](../../images/cycle-accuracy-comparison.svg) 71 | 72 | 仮に、CPUの1つの動作が常に2サイクルかかり、GPUが1サイクルごとにメモリからピクセルに値を読み出すという、単純なCPUアーキテクチャがあるとします。 73 | 74 | 理論的には、サイクル精度重視の設計では、これを1サイクルずつエミュレートしていきます。 75 | 76 | まず、CPUの前半の動作を行った後、GPUのピクセル描画を1回行います。 77 | 78 | 次に、CPUの後半の処理を行い、さらにピクセルの描画を行います。 79 | 80 | しかし、理論の下には多くの複雑な要素が隠されています。 81 | 82 | CPUの命令のどの部分がどのサイクルで起こるのかを知る必要がありますが、これは実装に依存することが多く、定義が不十分です。 83 | 84 | また、中途半端に終わった処理を保存しておき、後でそれに戻ることができなければなりません。 85 | 86 | これはアトミックデザインに比べてさらに複雑になり、結果的にパフォーマンスが低下します。 87 | 88 | アトミックデザイン(サイクルカウント精度重視)では、オペレーションはサイクルごとに分割されません。 89 | 90 | その代わり、それぞれのオペレーションは完了するまで実行され、その後、次のオペレーションを実行することができます。 91 | 92 | しかし、順序を間違えると、目に見える影響が出てきます。例えば、メモリへのアクセスがGPUの動作に干渉する場合、タイミングを誤ると正しくない画面になってしまいます。 93 | 94 | --- 95 | 96 | このような問題はありますが、独立な動作はサイクルカウント精度重視の設計では基本になっています。 97 | 98 | 複数の構成要素を扱う場合、サイクルカウント精度は、個々の構成要素の動作が正しいサイクル数を要し、操作が過去に発生したかのように見えるようにします。 99 | 100 | これらを組み合わせることで、完全ではありませんが、サイクル精度の近似値が得られます。 101 | 102 | サイクル精度に忠実なモデルでは、CPUの命令を先取りしたり中断したりすることはできず、CPUの命令の間に他のハードウェアの操作が行われます。 103 | 104 | しかし、これらのハードウェアビットが過去に現れるようにスケジュールすることで、すべてのビットが適切な時間を要することができます。 105 | 106 | この方法の最大の欠点は、元のハードウェアでは相互に作用する同時動作が適切にインターレースされないことです。 107 | 108 | しかし、システムが新しい場合や構成要素がシンプルな場合は、そのような考慮すべきような相互作用は非常に稀なものになります。 109 | 110 | GBAのような古いシステムにはエッジケースや複雑な相互作用がつきものですが、Nintendo Switchのようなモダンなシステムは一般的に慎重に設計されており、そのような相互作用が全く起こらないように保護されています。 111 | 112 | そのため、モダンなシステムは、サイクルカウント精度重視の設計に適しています。高速に実行でき、ほとんどの場合は十分に動作するからです。 113 | 114 | --- 115 | 116 | しかし、では、何をもって「十分」な性能とするのか、これは主観的で議論の余地のあるテーマです。 117 | 118 | 明らかなバグがなく遊べるゲームであれば、多くのプレイヤーは「十分」に良いと言うかもしれません。しかし、スピードランナーやTASの場合は、精度の低さが問題になってきます。 119 | 120 | ZSNESは、非常に長い間、SNESエミュレーションコミュニティの多くが、精度が低いながらも「十分」に良いと評価していました。 121 | 122 | 精度が低くても、多くのゲームはほぼ完璧に動作します。ZSNESが完全に崩壊してしまうようなエッジケースは常にありましたが、ほとんどの人気ゲームは十分にエミュレートされていたので、精度を上げることは優先されませんでした。 123 | 124 | 多くの人にとって、これは「十分」なことでした。今でもそうだと思う人もいるでしょう。しかし、完璧とは言えず、それが気に食わない人もいました。 125 | 126 | --- 127 | 128 | その結果、サイクル精度の高いエミュレータで、最も有名な例である [higan](https://byuu.org/emulation/higan/)が誕生しました。 129 | 130 | これは伝説的といっていいほど正確なものですが、同時に悪名高いほど遅いものでもあります。 131 | 132 | これは、サイクル精度を追求したものであることが原因のひとつです。 133 | 134 | higanは、必要に応じてエミュレーションの一部を切り替えるためにコルーチンを使用しており、これには多くのオーバーヘッドがあります。 135 | 136 | higanはサイクル精度重視なエミュレータの最も有名な例であるため、サイクル精度重視であることが必ずしも非常に遅いという誤解を招いています。 137 | 138 | しかし、higanのパフォーマンス問題の多くは、エミュレーションがスピードに最適化されていないことに起因しています。 139 | 140 | これは、byuu氏がドキュメントとしてのコードを厳格に管理しているため、最終的に読みやすく、理解しやすいコードにするための意図的な判断です。 141 | 142 | byuu氏は、higanをファミコンのゲームを遊ぶための手段としてだけでなく、ファミコンの挙動を記録する保存プロジェクトとしても位置づけています。 143 | 144 | 高度に最適化された正確なSNESエミュレータを作れば、正確さを犠牲にすることなく、higanよりもはるかに高速なエミュレータを作ることができますが、誰もそれを実現していません。なかなか大変な作業だと思います。 145 | 146 | --- 147 | 148 | [私](https://twitter.com/endrift)がmGBAを作り始めたときの目標は、**VisualBoyAdvanceよりもサイクル精度が正確で、かつ速い**というものでした。 149 | 150 | この2つの目標はしばしば相反するものですが、正確さにはタイミングだけでなく、もっと多くの要素があります。 151 | 152 | 画面のレンダリングが正しくないとか、メモリ操作が正しくエミュレートされていないなどの問題があるのです。 153 | 154 | VisualBoyAdvanceでは、最適化されていない部分が多く、サイクル精度を上げてもスピードに影響しない部分が多いと感じました。 155 | 156 | 速度が向上したことで、速度に影響を与えるサイクル精度の向上のためのオーバーヘッド(余裕)も確保できました。 157 | 158 | --- 159 | 160 | mGBAにはGBAとGBのエミュレーションが可能です。 161 | 162 | GBAのエミュレーションとは異なり、のGBエミュレーション(mGBと呼ばれることもあります)は、サイクル精度を考慮して設計されています。 163 | 164 | 命令エミュレーションは、個々のクロックサイクルで実行されるタスクに分割され、これらのオペレーションの間に他のハードウェアをエミュレートすることができます。 165 | 166 | しかし、演算を一度に実行するのではなく、バッチ処理を行う(同時並行的なインタラクションがある場合は、必要に応じてバッチを分割する)など、多くの最適化により、mGBは非常に高速に実行できるようになっています。 167 | 168 | 精度を犠牲にすることなく、最適化されていないサイクル精度重視な実装よりもはるかに高速です。 169 | 170 | --- 171 | 172 | サイクルカウント精度重視の実装が障害となる顕著な例として、今後mGBAで実装予定のDSエミュレーションのGPUコマンドFIFOが挙げられます。 173 | 174 | DSは、GPUにコマンドを書き込む際に、まだ処理されていないコマンドのリストを作成します。 175 | 176 | 新しいコマンドはこのリストに追加されます。 177 | 178 | ただし、FIFOには最大サイズがあります。 179 | 180 | FIFOが一杯になると、新しいコマンドのためにFIFOに十分なスペースができるまで書き込みがブロックされます。 181 | 182 | 実際のハードウェアでは、FIFOがいっぱいになると、メモリバスがストールし、ARM CPUはこの命令の途中で一時的にブロックされます。 183 | 184 | その後、メモリバスとは独立にFIFOがGPUによって読み込まれ、FIFOに空きができるのでメモリバスが続行できるようになります。 185 | 186 | mGBAのDSエミュレーション`medusa`ではCPUの動作はアトミックに扱われるので、命令の途中でメモリバスをストールさせてGPUでFIFOを処理することはできません。 187 | 188 | 代わりに、`medusa`が満杯のFIFOへの書き込みを処理する方法は、書き込むべき値をキャッシュし、FIFOに空きができてキャッシュされた値がFIFOにフラッシュされるまで、新しい命令を実行できないことをCPUに伝えるというものです。 189 | 190 | しかし、ARM CPUには、一度に複数の値をメモリに書き込むことができる命令群が用意されています。 191 | 192 | つまり、1つの命令で複数のコマンドをFIFOに書き込むことが可能であり、書き込みの間にメモリバスがストールする可能性もあります。 193 | 194 | 例えば、FIFOに3つのコマンドを書き込んだが、1つしか入らない場合、3つ目を書き込もうとする前にストールしてしまいます。 195 | 196 | これは`medusa`にとって大きな問題となりました。`medusa`は現在、実際にはサイクルカウントの精度に違反しているが、新しいコマンドを書き込むのに十分な量のFIFOをすぐに処理するというアプローチでこれに対処しています。 197 | 198 | これはある特定のエッジケースですが、正しくない動作なので対処する必要があります。 199 | 200 | --- 201 | 202 | サイクル精度からさらに進んで、ハイレベルエミュレーション(HLE)という概念があります。 203 | 204 | 多くのゲーム機は、特に90年代後半以降、エミュレートされたソフトウェア自体には含まれていないプログラマブルなコンポーネントを持っています。 205 | 206 | コンポーネントはシステム自体の一部であり、ゲームによって大きな違いはありません。 207 | 208 | 例えば、DSP(ゲームキューブに搭載)、システムソフトウェア(PSPや3DSに搭載)、NINTENDO64のRSPのようなマイクロコードプログラマブルデバイスなどです。 209 | 210 | これらのコンポーネントは、直接エミュレートすることもできますが(ローレベルエミュレーション(LLE)と呼ばれています)、実際には、命令ごとに段階的にエミュレートしなくても済ませることができます。 211 | 212 | ハードウェアと同じ効果を持つコンポーネントのカスタム実装を書き、それをエミュレートするのではなくネイティブコードとして実行することで、動作を大幅に高速化することができます。 213 | 214 | 一方で、異なるハードウェアコンポーネントとの同期や、適切なタイミングでの動作がほぼ不可能になるというデメリットもあります。 215 | 216 | さらに、HLEの実装には、ハードウェアそのものだけでなく、その上で動作するマイクロコードについても膨大な研究が必要です。 217 | 218 | HLEの初期の実装は、LLEにはないバグが多く、デバッグが非常に困難な場合があります。 219 | 220 | 一方、LLEの実装では、エミュレートされるコードのコピーが必要ですが、これは必ずしも容易ではなく、著作権の関係でエミュレータ本体と一緒に配布することもできません。 221 | 222 | --- 223 | 224 | 精度と速度のトレードオフは難しい問題です。 225 | 226 | 古いシステムでは、エミュレーションがすでにかなり高速で、通常はより厳しいタイミング制限があるため、ほとんどの場合、精度が優先されます。 227 | 228 | 最新のシステムでは、エミュレーションするためにはHLEが必要になります。バランスが難しいところですが、どちらにもメリットがあります。 229 | 230 | 一般的には、精度が高い方がエミュレートされたソフトウェアでの問題が少なく、保存のための価値も高くなりますが、スピードと最近のプラットフォームのエミュレーションのためには、精度は必ずしも必要ではありません。 231 | 232 | -------------------------------------------------------------------------------- /hqx-sharder/README.md: -------------------------------------------------------------------------------- 1 | # HQx-shader 2 | 3 | >**Note** 4 | > このドキュメントは [HQx-shader](https://github.com/CrossVR/hqx-shader/blob/53540f5f0d985c385dc108b41ab89980f2b214f4/README.md) のREADMEを日本語に翻訳したものです。 5 | 6 | ドット絵を高画質化(アップスケール)するフィルターHQxをCgシェーダーを用いて実装したものです。 7 | 8 | ## 使い方 9 | 10 | RetroArchなど、アップスケールに対応したエミュレータで、希望するアップスケールのプリセットファイルを読み込みます。 11 | 12 | ## エミュレータへの組み込み 13 | 14 | このシェーダのサポートをエミュレータに実装したい場合は、ゲーム画面のテクスチャの他に、シェーダ用の追加テクスチャのロードをサポートする必要があります。ロードする必要のあるテクスチャは、`resources`ディレクトリにあります。 15 | 16 | `resource`内の追加テクスチャのサポートを実装した後は、`single-pass/shader-files`ディレクトリにあるシングルパスシェーダを統合することができます。 17 | 18 | さらに、パフォーマンスを向上させるために、マルチパスシェーダをサポートすることができます。このシェーダは冗長な計算を減らし、シングルパス版よりもはるかに高速に動作します。マルチパスシェーダは`shader-files`ディレクトリにあり、デフォルトではプリセットファイルで使用されます。 19 | 20 | ## 実装 21 | 22 | このシェーダは、オリジナルのHQxフィルタと同様に、ルックアップテーブルを必要とします。HQxでは、このルックアップテーブルには、加重平均によるピクセルの補間に使われる重みが含まれています。オリジナルのアルゴリズムの詳細については、[ドキュメント](algorithm.md)をご覧ください。 23 | 24 | HQxアルゴリズムは、元のピクセル(w5)をその8つの近傍ピクセルで補間します。 25 | 26 | ``` 27 | +----+----+----+ 28 | | | | | 29 | | w1 | w2 | w3 | 30 | +----+----+----+ 31 | | | | | 32 | | w4 | w5 | w6 | 33 | +----+----+----+ 34 | | | | | 35 | | w7 | w8 | w9 | 36 | +----+----+----+ 37 | ``` 38 | 39 | すべてのピクセルはYUV色空間で比較され、検出されたパターンの重みがルックアップテクスチャから取得されます。補間に使用したいピクセルも、この重みによって選択されます。(他のピクセルの重みはゼロに設定されます) 40 | 41 | ルックアップテクスチャの各エントリには、重みを格納するための4つのコンポーネントがあります。重みは、単純な行列とベクトルの乗算によって適用されます。この行列に含まれるピクセルは、元のピクセルに対してどのアップスケールピクセルを処理しているかによって異なります。例えば、元のピクセルに対して左上にある場合は、w1、w2、w4、w5のピクセルを補間するだけで済みます。 42 | 43 | ## クレジット 44 | 45 | - このレポジトリ: LGPL-2.1 46 | - オリジナルのC実装: Maxim Stepin氏、Cameron Zemek氏 47 | - このレポジトリの実装: Hyllian氏(xBRの作者)が手助けしてくれました 48 | - その他サポート: Hunter K.氏 49 | 50 | -------------------------------------------------------------------------------- /hqx-sharder/algorithm.md: -------------------------------------------------------------------------------- 1 | # HQxアルゴリズム 2 | 3 | >**Note** 4 | > このドキュメントは [hqxのREADMEのImplementation](https://code.google.com/archive/p/hqx/wikis/ReadMe.wiki) を日本語に翻訳したものです。 5 | 6 | 最初のステップは、ソースピクセルの3x3エリアの分析です。まず、中心となるピクセルとその8つの近傍ピクセルとの色差を計算します。そして、その差を事前に設定した閾値と比較し、これらのピクセルを2つのカテゴリーに分類します。"近い色" と "遠い色" です。近傍は8つあるので、256通りの組み合わせが可能です。 7 | 8 | 次のステップであるフィルタリングには、[256のエントリを持つルックアップテーブル](https://github.com/grom358/hqx/blob/124c9399fa136fb0f743417ca27dfa2ca2860c2d/src/hq2x.c#L151)が使用されます。これは、近い/遠い色の近傍ピクセルの組み合わせごとに1つのエントリです。各エントリには、フィルタリングされた画像の補間されたピクセルを得るために、3x3エリアのソースピクセルの色をミックスする方法が記述されています。 9 | 10 | 現在の実装では、YUV色空間を使用して色差を計算しており、Y(明るさ)成分とUおよびVの色成分の許容度が高くなっています。この色空間の変換は、ソース画像のフォーマットが1ピクセルあたり16bitであれば、[単純なルックアップテーブル](https://github.com/grom358/hqx/blob/124c9399fa136fb0f743417ca27dfa2ca2860c2d/src/init.c#L22)を使用して非常に簡単に実装できます。また、MMX命令を使って色差を計算し、閾値と比較することも非常に高速に行うことができます。 11 | 12 | 256エントリのルックアップテーブルの作成は最も難しい部分でした。画像の異なる色の領域間のエッジを維持し、エッジの方向をできるだけ正しいものに近づけるという考えのもと、各組み合わせについて、その領域の最も可能性の高いベクトル表現を決定しなければなりません。このベクトル表現は、アンチエイリアスを用いてより高い(3倍)解像度でラスタライズされ、その結果がルックアップテーブルに格納されます。 13 | 14 | 写真ではなく、ライングラフィックスやカートゥーンのスプライトのように、エッジがはっきりした画像を対象にしています。また、256×256の画像をリアルタイムで処理できるよう、高速性も重視しました。 15 | 16 | -------------------------------------------------------------------------------- /hqx-sharder/sample/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! -------------------------------------------------------------------------------- /hqx-sharder/sample/README.md: -------------------------------------------------------------------------------- 1 | # サンプルコードの解説 2 | 3 | >**Note** 4 | > このドキュメントは [`hqx-shader/sample/main.cpp/`](https://github.com/CrossVR/hqx-shader/blob/53540f5f0d985c385dc108b41ab89980f2b214f4/sample/main.cpp) を日本語で解説したものです。 5 | 6 | ## ソースコード 7 | 8 | ```cpp 9 | /* main.c 10 | * 11 | * Copyright (C) 2017 Jules Blok 12 | * 13 | * This software may be modified and distributed under the terms 14 | * of the MIT license. See the LICENSE file for details. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include "lodepng.h" 21 | #include "linmath.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef _WIN32 29 | #define _ "\\" 30 | #else 31 | #define _ "/" 32 | #endif 33 | 34 | static const struct 35 | { 36 | float x, y, z, w; 37 | float u, v, s, t; 38 | } vertices[] = 39 | { 40 | { -1.f, -1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 0.f }, 41 | { -1.f, 1.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f }, 42 | { 1.f, 1.f, 0.f, 1.f, 1.f, 0.f, 0.f, 0.f }, 43 | { 1.f, -1.f, 0.f, 1.f, 1.f, 1.f, 0.f, 0.f } 44 | }; 45 | 46 | // 1xの頂点シェーダー 47 | static const char* vertex_shader_text = 48 | "attribute vec4 VertexCoord;\n" 49 | "attribute vec4 TexCoord;\n" 50 | "varying vec2 tex;\n" 51 | "void main()\n" 52 | "{\n" 53 | " gl_Position = VertexCoord;\n" 54 | " tex = TexCoord.xy;\n" 55 | "}\n"; 56 | 57 | // 1xのフラグメントシェーダー 58 | static const char* fragment_shader_text = 59 | "uniform sampler2D Texture;\n" 60 | "varying vec2 tex;\n" 61 | "void main()\n" 62 | "{\n" 63 | " gl_FragColor = texture2D(Texture, tex);\n" 64 | "}\n"; 65 | 66 | // HQxのシェーダー 67 | static const char* shader_files[] = { 68 | _"glsl" _"hq2x.glsl", 69 | _"glsl" _"hq3x.glsl", 70 | _"glsl" _"hq4x.glsl" 71 | }; 72 | 73 | // LUTテクスチャ(シェーダ用の追加テクスチャ) 74 | static const char* lut_files[] = { 75 | _"resources" _"hq2x.png", // resource/hq2x.png 256x64 76 | _"resources" _"hq3x.png", // resource/hq3x.png 256x144 77 | _"resources" _"hq4x.png" // resource/hq4x.png 256x256 78 | }; 79 | 80 | // インデックス指標(どの頂点を結んで線分を描くか)を保存する配列(IBO, EBO) 81 | // 参照: https://qiita.com/y_UM4/items/8b87e82c66c185905553 82 | static const uint8_t indices[] = { 0, 1, 2, 0, 2, 3 }; 83 | 84 | static uint32_t image_width, image_height, image_scale = 2; 85 | 86 | static void error_callback(int error, const char* description) 87 | { 88 | // ... 89 | } 90 | 91 | static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) 92 | { 93 | // ... 94 | } 95 | 96 | // ファイルをバイト列としてbufferに読み込む 97 | static void read_file(const char* filename, std::vector& buffer) 98 | { 99 | std::ifstream file(filename, std::ios::ate); 100 | if (!file.is_open()) { 101 | std::cout << "Failed to open " << filename << std::endl; 102 | exit(EXIT_FAILURE); 103 | } 104 | 105 | std::streamsize size = file.tellg(); 106 | file.seekg(0, std::ios::beg); 107 | 108 | buffer.resize(size); 109 | file.read(buffer.data(), size); 110 | } 111 | 112 | static GLuint load_texture(uint32_t* width, uint32_t* height, const char* filename) 113 | { 114 | std::vector image; 115 | uint32_t w, h, error; 116 | GLuint texture; 117 | 118 | error = lodepng::decode(image, w, h, filename); 119 | if (error) { 120 | error_callback(error, lodepng_error_text(error)); 121 | exit(EXIT_FAILURE); 122 | } 123 | 124 | glGenTextures(1, &texture); 125 | glActiveTexture(GL_TEXTURE9); // loading stage 126 | glBindTexture(GL_TEXTURE_2D, texture); 127 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data()); 128 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 129 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 130 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); 131 | 132 | if (width) *width = w; 133 | if (height) *height = h; 134 | return texture; 135 | } 136 | 137 | static GLuint compile_shader(GLenum stage, const GLchar* source) 138 | { 139 | GLchar* error_log; 140 | GLint compiled, length; 141 | GLuint shader; 142 | const GLchar* sources[2] = { "#version 130\n", source }; 143 | 144 | // Both stages are present in the same file, use the pre-processor to separate them 145 | if (stage == GL_VERTEX_SHADER) sources[0] = "#version 130\n#define VERTEX\n"; 146 | if (stage == GL_FRAGMENT_SHADER) sources[0] = "#version 130\n#define FRAGMENT\n"; 147 | 148 | shader = glCreateShader(stage); 149 | glShaderSource(shader, 2, sources, NULL); 150 | glCompileShader(shader); 151 | 152 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 153 | if (compiled == GL_FALSE) { 154 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); 155 | error_log = new char[length]; 156 | glGetShaderInfoLog(shader, length, &length, error_log); 157 | error_callback(GL_INVALID_OPERATION, error_log); 158 | delete error_log; 159 | } 160 | 161 | return shader; 162 | } 163 | 164 | // シェーダープログラムを作成 165 | static GLuint link_program(GLuint vertex_shader, GLuint fragment_shader) 166 | { 167 | GLchar* error_log; 168 | GLint compiled, length; 169 | GLuint program; 170 | 171 | program = glCreateProgram(); 172 | glAttachShader(program, vertex_shader); 173 | glAttachShader(program, fragment_shader); 174 | glLinkProgram(program); 175 | 176 | glGetProgramiv(program, GL_LINK_STATUS, (int *)&compiled); 177 | if (compiled == GL_FALSE) { 178 | glGetShaderiv(program, GL_INFO_LOG_LENGTH, &length); 179 | error_log = new char[length]; 180 | glGetProgramInfoLog(program, length, &length, error_log); 181 | error_callback(GL_INVALID_OPERATION, error_log); 182 | delete error_log; 183 | } 184 | 185 | // We don't need the shaders anymore 186 | glDeleteShader(vertex_shader); 187 | glDeleteShader(fragment_shader); 188 | 189 | return program; 190 | } 191 | 192 | int main(int argc, const char* argv[]) 193 | { 194 | if (argc < 2) { 195 | std::cout << "Usage: " << argv[0] << " [image file]" << std::endl; 196 | exit(EXIT_FAILURE); 197 | } 198 | 199 | // Set up some basic paths based on the input arguments 200 | std::string base_path = argv[1]; 201 | std::string image_path(base_path); 202 | if (argc > 2) 203 | image_path = argv[2]; 204 | else 205 | image_path.append(_"sample" _"pixelart0.png"); 206 | 207 | // Initialise GLFW and create the window 208 | glfwSetErrorCallback(error_callback); 209 | if (!glfwInit()) exit(EXIT_FAILURE); 210 | 211 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 212 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 213 | 214 | GLFWwindow* window = glfwCreateWindow(640, 480, "HQx Sample", NULL, NULL); 215 | if (!window) { 216 | glfwTerminate(); 217 | exit(EXIT_FAILURE); 218 | } 219 | 220 | glfwSetKeyCallback(window, key_callback); 221 | 222 | glfwMakeContextCurrent(window); 223 | gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); 224 | glfwSwapInterval(1); 225 | 226 | // アップスケールする画像をテクスチャ(変数`texture`)として読み込む 227 | GLuint texture = load_texture(&image_width, &image_height, image_path.c_str()); // image_path: アップスケール対象の画像 e.g. sample/pixelart0.png 228 | glActiveTexture(GL_TEXTURE0); 229 | glBindTexture(GL_TEXTURE_2D, texture); 230 | 231 | // 頂点バッファ(VBO)にフルスクリーンの四角形を読み込む 232 | GLuint vertex_buffer; 233 | glGenBuffers(1, &vertex_buffer); 234 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); 235 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 頂点バッファオブジェクトのメモリを確保し、そこにデータ (頂点属性) を転送 236 | 237 | // すべてのアップスケール(HQx)シェーダーを含むベクタを初期化する 238 | // ベクタのインデックスはスケール倍率を表す 239 | std::vector programs, lut_textures; 240 | programs.push_back(NULL); // programs: シェーダープログラムを格納するベクタ 241 | lut_textures.push_back(NULL); // lut_textures: 242 | 243 | // 何もしないシェーダー(1xシェーダー)をロードする 244 | GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_text); 245 | GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_text); 246 | programs.push_back(link_program(vertex_shader, fragment_shader)); 247 | lut_textures.push_back(NULL); // no lookup table set 248 | 249 | // 1xシェーダーのUniform変数を設定 250 | glUniform1i(glGetUniformLocation(programs[1], "Texture"), 0); 251 | GLint vpos_location = glGetAttribLocation(programs[1], "VertexCoord"); 252 | GLint vtex_location = glGetAttribLocation(programs[1], "TexCoord"); 253 | 254 | // attribute変数を設定。 255 | // すべてのシェーダーは互換性のあるシグネチャを持っているので、この作業は一度しか行われない 256 | glEnableVertexAttribArray(vpos_location); 257 | glVertexAttribPointer(vpos_location, 4, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), (void*)0); 258 | glEnableVertexAttribArray(vtex_location); 259 | glVertexAttribPointer(vtex_location, 4, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), (void*)(sizeof(float) * 4)); 260 | 261 | // アップスケールシェーダー(HQx)をロードする 262 | mat4x4 mvp; 263 | mat4x4_identity(mvp); 264 | for (int i = 0; i < 3; i++) { 265 | std::vector shader; // HQxシェーダーのソースコード 266 | std::string shader_path(base_path); 267 | shader_path.append(shader_files[i]); // e.g. shader_path = glsl/hq2x.glsl 268 | 269 | // HQxシェーダープログラム(program)の作成 270 | read_file(shader_path.c_str(), shader); 271 | vertex_shader = compile_shader(GL_VERTEX_SHADER, shader.data()); 272 | fragment_shader = compile_shader(GL_FRAGMENT_SHADER, shader.data()); 273 | GLuint program = link_program(vertex_shader, fragment_shader); 274 | 275 | // HQxシェーダーのUniform変数ポインタを取得 276 | GLint mvp_location = glGetUniformLocation(program, "MVPMatrix"); 277 | GLint samp_location = glGetUniformLocation(program, "Texture"); 278 | GLint lut_location = glGetUniformLocation(program, "LUT"); 279 | GLint tsize_location = glGetUniformLocation(program, "TextureSize"); 280 | 281 | // HQxシェーダーのUniform変数を設定 282 | glUseProgram(program); 283 | glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat*)mvp); 284 | glUniform1i(samp_location, 0); 285 | glUniform1i(lut_location, 1); 286 | glUniform2f(tsize_location, (float)image_width, (float)image_height); 287 | 288 | // LUTテクスチャのロード 289 | std::string lut_path(base_path); 290 | lut_path.append(lut_files[i]); // e.g. lut_path = resource/hq2x.png 291 | GLuint lut = load_texture(nullptr, nullptr, lut_path.c_str()); 292 | 293 | // ベクタにpush 294 | programs.push_back(program); 295 | lut_textures.push_back(lut); 296 | } 297 | 298 | // ウィンドウのサイズをデフォルトの縮尺(画像サイズの2倍)に変更し、レンダリングループに入る 299 | glfwSetWindowSize(window, image_width * image_scale, image_height * image_scale); 300 | while (!glfwWindowShouldClose(window)) { 301 | // 指定されたウィンドウに割り当てられているフレームバッファのサイズを調べる 302 | int width, height; 303 | glfwGetFramebufferSize(window, &width, &height); 304 | 305 | glViewport(0, 0, width, height); // ビューポート: コンピューターグラフィックの中で現在表示されている領域 306 | glClear(GL_COLOR_BUFFER_BIT); 307 | 308 | // アップスケールテクスチャの適用 309 | glUseProgram(programs[image_scale]); 310 | glActiveTexture(GL_TEXTURE1); // 事前にGL_TEXTURE0にスケール対象の画像を読み込んでいる 311 | glBindTexture(GL_TEXTURE_2D, lut_textures[image_scale]); 312 | 313 | // 四角形(ゲームスクリーン)の描画 314 | // 三角形(GL_TRIANGLES)2つの描画で四角形を表現するので必要な頂点数(第2引数)は6 315 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices); 316 | 317 | glfwSwapBuffers(window); 318 | glfwPollEvents(); 319 | } 320 | 321 | glfwDestroyWindow(window); 322 | 323 | glfwTerminate(); 324 | exit(EXIT_SUCCESS); 325 | } 326 | ``` -------------------------------------------------------------------------------- /ips_file_format.md: -------------------------------------------------------------------------------- 1 | # IPSファイル 2 | 3 | >**Note** 4 | > アドレスと長さはビッグエンディアンで格納されます。 5 | 6 | ## フォーマット 7 | 8 | ```rust 9 | struct IPS { 10 | // ASCII文字列"PATCH" 11 | magic: String; 12 | 13 | // 任意の数の、`Record` and/or RLEレコード`RLERecord`(後述) 14 | // RecordKind = Record | RLERecord 15 | records: Vec; 16 | 17 | // ASCII文字列"EOF" 18 | eof: String; 19 | 20 | // Truncateされたデータ(後述) 21 | truncate: Vec; 22 | } 23 | ``` 24 | 25 | ## `Record`フォーマット 26 | 27 | ```rust 28 | struct Record { 29 | // 3バイト、パッチ対象の開始オフセットを表す 30 | offset: [u8; 3]; 31 | 32 | // 2バイト、パッチデータの長さを示す 33 | length: [u8; 2]; 34 | 35 | // パッチデータ(サイズは`.length`バイト) 36 | // `.offset`の示すオフセットから`.length`バイトだけ書き込まれる 37 | data: Vec; 38 | } 39 | ``` 40 | 41 | ## `RLERecord`フォーマット 42 | 43 | RLEレコードは、1つのバイトを複数回書き込みます。 44 | 45 | ```rust 46 | struct RLERecord { 47 | // 3バイト、パッチ対象の開始オフセットを表す 48 | offset: [u8; 3]; 49 | 50 | // 2バイト、両方とも0で、RLERecordであることを示唆するためのマジックナンバー 51 | magic: [u8; 2]; // [0, 0] 52 | 53 | // 2バイト、パッチデータの長さを示す 54 | length: [u8; 2]; 55 | 56 | // パッチデータ 57 | // offsetからlengthバイトバイトだけ繰り返し書き込まれる 58 | data: u8; 59 | } 60 | ``` 61 | 62 | ## Truncate 63 | 64 | SNESToolや他のIPSパッチャー(Lunar IPS, NINJAなど)では、ファイル切り捨て機能をサポートしています。 65 | 66 | この機能は、EOFの後に3バイトのオフセットを格納する形で、IPSフォーマットを拡張したものです。この機能をサポートしているIPSパッチャーは、パッチされたファイルを切り捨て、オフセットの後のデータをすべて削除します。 67 | 68 | ## 参考記事 69 | 70 | - [IPS file format](http://www.smwiki.net/wiki/IPS_file_format) 71 | -------------------------------------------------------------------------------- /libretro/README.md: -------------------------------------------------------------------------------- 1 | # Libretro Docs 2 | 3 | https://github.com/libretro/docs/tree/952b6f0343995e6910b7840b0d415c508f366c9a/docs/development 4 | -------------------------------------------------------------------------------- /libretro/cores/developing-cores.md: -------------------------------------------------------------------------------- 1 | # Libretorコアの開発 2 | 3 | ## 🖲 Libretro API 4 | 5 | Libretro APIは、汎用のオーディオ、ビデオ、入力コールバックを公開する、軽量なCベースのアプリケーション・プログラミング・インターフェース(API)です。スタンドアロンゲーム、ゲームエミュレータ、メディアプレイヤーなどの「コア」の開発者は、Direct3DやOpenGL用の異なるビデオドライバを書いたり、可能なすべての入力API、サウンドAPI、ゲームパッドなどに対応することを心配する必要はありません。 6 | 7 | Libretro APIの利用を選択すると、あなたのプログラムは1つのライブラリファイル(「libretro core」と呼ばれます)になります。Libretro APIをサポートするフロントエンドは、そのライブラリファイルを読み込んでアプリを実行することができます。フロントエンドの責任は、実装特有の詳細を提供することです。libretro coreの役割は、メインプログラムを提供することだけです。 8 | 9 | このAPIで動作するように移植されたプロジェクトは、今も昔も、どのlibretroフロントエンドでも動作させることができます。メインプログラムだけを扱う単一のコードベースを維持し、単一のAPI(libretro)をターゲットにして、プログラムを一度に複数のプラットフォームに移植することができます。移植性の高いCやC++で書かれたlibretro coreは、ほとんど、あるいは全く移植の手間をかけずに、多くのプラットフォーム上でシームレスに動作します。他の言語のためのlibretroバインディングも、ますます一般的で包括的になってきています。 10 | 11 | 注意: Libretroは、ライセンス料や縛りがなく、100%無料で実装できるオープンな仕様となっています。私たちのリファレンスフロントエンドは RetroArch です。この2つのプロジェクトは同じものではなく、これはライセンスに反映されています。RetroArch は GPLv3 でライセンスされていますが、libretro API は MIT ライセンスの API です。 12 | 13 | ## 📚 コア開発のためのリソース一覧 14 | 15 | ### `libretro.h` 16 | 17 | `libretro.h`は、libretroのコアやフロントエンドの開発者にとって、最も重要な技術的リファレンスです。 18 | 19 | `libretro.h`の最新の標準的なコピーは、`libretro-common`リポジトリの masterブランチにあります。 20 | 21 | ### サンプルコア実装の`skeletor` 22 | 23 | RetroArchのコントリビュータである bparker06氏は、最小限のlibretroコアの実装として`skeletor`を作成しました。 24 | 25 | `skeletor`は、スタブのlibretro `Makefile`と`Makefile.common`ファイルも提供しています。 26 | 27 | ### `Vectrexia` codebase and development log 28 | 29 | beardypig氏は、libretroのために一から設計されたオリジナルのエミュレータコアである`Vectrexia`の作成の一環として、`libretro.h`の実装プロセスを説明する2部構成のガイドを公開しました。([Part 1](https://web.archive.org/web/20190219134430/http://www.beardypig.com/2016/01/15/emulator-build-along-1/), [Part 2](https://web.archive.org/web/20190219134028/http://www.beardypig.com/2016/01/22/emulator-build-along-2/)) 30 | 31 | ### `libretro-common` 32 | 33 | [`libretro-common`](https://github.com/libretro/libretro-common/) は、libretro のコアやフロントエンドの開発に役立つ、クロスプラットフォームの必須コーディングブロックを集めたもので、主にC言語で書かれています。 34 | 35 | ### `libretro-deps` 36 | 37 | [`libretro-deps`](https://github.com/libretro/libretro-deps/) は、libretro コアで使用するために事前に変更されたサードパーティの依存関係のコレクションです。 38 | 39 | ### `libretro-samples` 40 | 41 | [`libretro-samples`](https://github.com/libretro/libretro-samples) は、libretro APIの実装例です。 42 | 43 | ### OpenGLによってハードウェアアクセラレートされたコアの実装について 44 | 45 | [OpenGLによってハードウェアアクセラレートされたコアの実装ガイド](https://github.com/libretro/docs/blob/952b6f0343995e6910b7840b0d415c508f366c9a/docs/development/cores/opengl-cores.md)が利用可能です。 46 | 47 | ## 🛠 Libretro APIの実装 48 | 49 | [こちら](implementing-the-api.md)を参照 50 | 51 | -------------------------------------------------------------------------------- /libretro/cores/implementing-the-api.md: -------------------------------------------------------------------------------- 1 | # Libretorコアの開発 \~Libretro APIの実装\~ 2 | 3 |
  4 | Note: 
  5 | 
  6 | この記事はLibretorコアの開発からAPIの一覧部分を抜き出したものです。
  7 | 
8 | 9 | libretro APIは、RetroArchのソースパッケージにある`libretro.h`に記載されている複数の関数から成り立っています。 10 | 11 | libretro の実装では、`libretro.h` で記述されている関数をすべてエクスポートした動的ロード可能な実行ファイル (`.dll/.so/.dylib`) または静的ライブラリ (`.a/.lib`) にコンパイルする必要があります。 12 | 13 | 実装はシングルインスタンスであることを前提としていますので、グローバルなステートの存在も許容されます。また、フロントエンドがこれらの関数を間違った順序で呼び出すと、未定義の動作が発生することに注意してください。 14 | 15 | APIヘッダはC99とC++に対応しています。C99では、bool型と``を使用しています。 16 | 17 | libretro APIを用いたフロントエンドのプログラムフローは、以下のように表現できます。 18 | 19 | ## 🧨 起動時 20 | 21 | ### `retro_api_version()` 22 | 23 | この関数は、`libretro.h`で定義されている`RETRO_API_VERSION`を返す必要があります。 24 | 25 | この関数は、ABI/APIが不一致であるかどうかを判断するためにフロントエンドによって使用されます。 26 | 27 | APIに互換性のない変更があった場合は、バージョンが変更されます。`retro_*`構造体への変更や、公開されている関数やその引数への変更は、APIバージョンのバンプ(バージョン更新)を保証します。 28 | 29 | ### `retro_set_*()` 30 | 31 | Libretroはコールバックベースです。 32 | 33 | フロントエンドはこの段階ですべてのコールバックを設定し、実装側はこれらの関数ポインタをどこかに保存しなければなりません。 34 | 35 | フロントエンドは、後からこれらのコールバックを呼び出すことができます。 36 | 37 | ### `retro_init()` 38 | 39 | この関数は一度だけ呼ばれ、実装側にデータ構造を初期化する機会を与えます。 40 | 41 | ### 環境コールバック 42 | 43 | Libretroにはビデオ、オーディオ、入力用のコールバックがありますが、環境コールバックと呼ばれるコールバックもあります。 44 | 45 | このコールバック(`retro_environment_t`)は、libretroの実装側がAPIの機能にアクセスするための一般的な方法で、独自のシンボルを付けるにはあまりにも不明瞭であると考えられています。 46 | 47 | これはABIを壊すことなく拡張することができます。このコールバックは、フロントエンドが与えられたリクエストを認識したかどうかを示す`bool`型の戻り値を持っています。 48 | 49 | ### `retro_set_controller_port_device()` 50 | 51 | デフォルトでは、ゲームパッドが実装側(のエミュレートするハードウェア)に接続されていると想定されます。 52 | 53 | コアがどのタイプの入力デバイスが差し込まれているかに敏感な場合は、フロントエンドがこの関数を呼び出して、特定のプレイヤーに対して使用するデバイスを設定することがあります。 54 | 55 | 実装側では、可能であればこれを自動検出するようにしてください。 56 | 57 | ### `retro_get_system_info()` 58 | 59 | フロントエンドは通常、実装の名前やバージョン番号など、コアに関する静的に知られている情報を要求します。返された情報は、静的に割り当てられていなければなりません。 60 | 61 | ### `retro_load_game()` 62 | 63 | この関数はコンテンツをロードします。 64 | 65 | 実装がエミュレータであれば、ゲームのROMイメージ、ゲームエンジンであれば、ゲーム用のパッケージ化されたアセットなどが考えられます。 66 | 67 | この関数は、ROMがロードされたパスを示す構造体と、すでにロードされたファイルのメモリチャンクを受け取ります。 68 | 69 | **libretroでのファイルの読み込みには2つのモードがあります。** ゲームエンジンが ROMイメージがどこからロードされたかのパスを知る必要がある場合、`retro_system_info`の`need_fullpath`フィールドを`true`に設定する必要があります。パスが必要な場合、フロントエンドは `data/size`フィールドにファイルをロードせず、ディスクからファイルをロードするかどうかは実装次第となります。パスには相対パスと絶対パスがありますが、実装では両方のケースをチェックする必要があります。この機能は、ROMイメージが大きすぎて一度にメモリにロードできない場合に有効です。また、コンテンツが多くの小さなファイルで構成されている場合、他のファイルのパスを推論するためにマスターファイルのパスを知る必要がある場合にも便利です。 70 | 71 | `need_fullpath`を`false`に設定すると、フロントエンドはあらかじめROMイメージをメモリにロードしておきます。このモードでは、`path`フィールドが非NULLであることは保証されません。ファイルが実際にディスクから読み込まれた場合には有効なパスを指すはずですが、 標準入力などから読み込まれた場合には明確なパスが存在しない可能性があります。可能であれば、`need_fullpath`を`false`に設定することをお勧めします。これにより、ソフトパッチなどの機能が正しく動作するようになります。 72 | 73 | ### `retro_get_system_av_info()` 74 | 75 | この関数は、フロントエンドにゲームの重要なオーディオ/ビデオのプロパティを知らせるものです。 76 | 77 | この情報は読み込まれたゲームに依存することがあるため、有効なROMイメージが読み込まれた後にのみ問い合わせが行われます。 78 | 79 | FFmpegによる録画は、数時間同期して実行できるように正確な情報に依存しているため、FPSとオーディオサンプリングレートを正確に報告することが重要です。 80 | 81 | ## 💥 実行時 82 | 83 | ### `retro_run()` 84 | 85 | ゲームが正常にロードされた後、`retro_run()`はユーザーが望む限り、繰り返し呼び出されます。 86 | 87 | 呼び出されると、実装側は1ビデオフレーム分、そのコアを実行させます。 88 | 89 | この間、実装側はビデオフレームやオーディオサンプルのコールバックを自由に呼び出すことができ、また入力をポーリングしたり、現在の入力状態を問い合わせたりすることができます。 90 | 91 | コールバックの要件は、ビデオコールバックが一度だけ呼ばれることです。最後に呼ばれる必要はありません。 92 | 93 | また、入力ポーリングは少なくとも一度は呼び出される必要があります。 94 | 95 | ### 入力 96 | 97 | ゲームパッドなどの入力デバイスの抽象化は、システムごとに異なるため、マルチシステムのAPIを定義する上で最も難しい部分です。 98 | 99 | そこでlibretro APIでは、ゲームパッドやジョイスティックを抽象化した`RetroPad`を提供し、プラットフォーム固有の入力コードを使わずにコアを記述できるようにしています。 100 | 101 | また、キーボード、マウス、ポインター、ライトガンなどの入力デバイスの抽象化も可能です。 102 | 103 | **詳細は、[Input APIのドキュメント](https://github.com/libretro/docs/blob/master/docs/development/input-api.md)をご覧ください。** 104 | 105 | ### Video/Audio synchronization considerations 106 | 107 | Libretroは固定レートを基本としており、ビデオのFPSやオーディオのサンプリングレートは常に一定であることを前提としています。 108 | 109 | フロントエンドは再生速度をコントロールします。通常はVSyncを使って正しい速度を得ます。 110 | 111 | フロントエンドは、「早送り」、つまり、待ち時間なしにできるだけ早く再生することや、スローモーションにすることができます。 112 | 113 | このような理由から、エンジンはシステムタイマーに依存して任意の同期を行うべきではありません。 114 | 115 | これは一般的なことで、3Dゲームでは、再生可能なゲームを維持しながらフレームレートの変化を考慮するためにしばしば必要となります。 116 | 117 | しかし、libretro は100%のリアルタイム性能が常に得られる、つまり処理落ちなどがない、と想定できる古いゲームコンソールなどのシステムを対象としているため、慎重なタイミングコードの必要性はありません。 118 | 119 | デフォルトでは、libretroの実装は、任意の`sleep()`や`time()`パターンを、単にビデオ/オーディオのコールバックの呼び出しに置き換えるべきです。 120 | 121 | フロントエンドでは、適切な同期が行われるようになっています。これは主に、PrBoomのようなゲームの移植版で問題となります。タイマーとスリープパターンに大きく依存していたPrBoomのlibretro移植版では、スリープを単に1フレーム実行してビデオコールバックを呼び出すことに置き換えました。その後、1フレーム分の時間(1/fps秒)に対応するだけの音声がレンダリングされました。スリープやタイミングのパターンはすべて取り除かれ、同期も正しく行われるようになりました。 122 | 123 | ### オーディオコールバックについて 124 | 125 | libretro APIには、2種類のオーディオコールバックがあります。どちらか一方のみを使用し、実装側でどちらのコールバックが最適かを選択する必要があります。 126 | 127 | 1つのオーディオフレームは、常に2つの`int16_t`のサンプルで構成されており、それぞれが左チャンネルと右チャンネルに対応しています。 128 | 129 | #### Per-sample audio 130 | 131 | 最初のオーディオコールバックは`per-sample`と呼ばれていますが、実際には1つのオーディオフレームを扱います。 132 | 133 | このコールバックは次のシグネチャを持っています。`void (*retro_audio_sample_t)(int16_t left, int16_t right)` 134 | 135 | これは、実装側が一度に1つのオーディオフレームを生成する場合に使用されるべきです。フロントエンドは、オーディオデータを適切なチャンクに分割して、システムコールのオーバーヘッドが大きくならないようにします。 136 | 137 | #### Batch audio 138 | 139 | オーディオが「バッチ」方式で出力されている場合、つまり一度に1/fps秒分のオーディオデータが出力されている場合は、バッチアプローチを考慮する必要があります。 140 | 141 | すべてのオーディオフレームをループして、サンプルごとのコールバックを毎回呼び出すのではなく、バッチコールバックを使用してください。 142 | 143 | このコールバックは次のシグネチャを持っています。`size_t (*retro_audio_sample_batch_t)(const int16_t * samples, size_t num_frames)` 144 | 145 | このコールバックによって供給されるサンプル数は `2 * num_frames` であるべきで2の倍数なのは左右のチャンネルに対応しています。 146 | 147 | バッチコールバックを使用すると、オーディオは一時的なバッファにコピーされないため、わずかながらパフォーマンスが向上します。また、すべてのデータが一度にオーディオドライバにプッシュされるので、若干のオーバーヘッドを節約できます。 148 | 149 | 非常に少量のデータ(32フレーム以下)にバッチコールバックを使用することはお勧めできません。バッチコールバックに渡されるデータは、オーディオのSIMD処理を高速化するために、可能であれば16バイト(プラットフォームによって異なります)にアラインメントする必要があります。 150 | 151 | ### `retro_serialize_size()`, `retro_serialize()`, `retro_unserialize()` 152 | 153 | シリアライズの実装はオプションです。シリアライズは、エミュレータでは「状態の保存」としてよく知られていますが、これらの関数は、固定量の状態を持つエミュレータでは確かに便利です。 154 | 155 | この機能により、フロントエンドはすべての内部状態のスナップショットを取り、後でそれを復元することができます。この機能は、巻き戻しやネット対戦の実装に使用されます。 156 | 157 | これらの機能をうまく実装するためには、いくつかの重要な注意事項があります。 158 | 159 | - シリアライズがサポートされていない場合、`retro_serialize_size()`は`0`を返すべきです。`retro_serialize_size()`が`0`以外の値を返す場合、フロントエンドはシリアライズが適切に実装されていると考えます。 160 | - フロントエンドは、正しくシリアライズするために必要なメモリ量を決定するために、`retro_serialize()`を呼び出す前に、`retro_serialize_size()`を呼び出す必要があります。 161 | - 最終的に `retro_serialize()` に渡されるサイズは、少なくとも `retro_serialize_size()` で返される値のサイズでなければなりません。大きすぎるバッファが `retro_serialize()` に渡された場合、余分なデータは無視されるか`0`に設定されるはずです。 162 | - `retro_serialize_size()`が返す値は、時間とともに減少する可能性はありますが、増加することはありません。これは、`retro_load_game()`の直後に、次のシリアライズを保持するのに十分な大きさの固定されたセーブステートのサイズを事前に決定する能力によって合理化されます。この確実性は巻き戻し実装の基本です。この要件は、`retro_load_game()`と`retro_unload_game()`の呼び出しの間でのみ有効です。 163 | 164 | 可能であれば、メモリバッファ内の一貫したオフセットでデータをシリアライズするように実装する必要があります。 165 | 166 | これにより、RetroArchの巻き戻し実装がより少ないメモリで済むようになります。 167 | 168 | `retro_serialize()` と `retro_unserialize()` はどちらも、実装がシリアライズやアンシリアライズに成功したかどうかをフロントエンドに知らせるために、ブール値を返します。 169 | 170 | ## 💧 終了時 171 | 172 | ### `retro_unload_game()` 173 | 174 | ユーザーがプレイを終了する際は、`retro_unload_game()`が呼び出されます。 175 | 176 | これにより、ゲームに関連する内部データが解放され、`retro_load_game()`が再び呼べるようになります。 177 | 178 | ### `retro_deinit()` 179 | 180 | この関数は、`retro_init()`中に初期化されたすべての状態を解放します。この関数を呼び出した後、フロントエンドは再び `retro_init()` を呼び出すことができます。 181 | 182 | ## 🧶 マルチスレッド時の安全性について 183 | 184 | libretro API はスレッドの安全性については保証しません。 185 | 186 | したがって、コアの開発者は libretroヘッダで宣言された関数がリエントラントではなく、また複数のスレッドから同時に呼び出されても安全ではないと考えるべきです。 187 | 188 | コアがマルチスレッドの場合、コアの開発者は libretro APIを呼び出す際のスレッドの安全性について責任を負います。 189 | 190 | `retro_run()`の外で、つまりメインスレッドの外で libretro APIコールを行うことは推奨されません。 191 | 192 | -------------------------------------------------------------------------------- /libretro/libretro-overview.md: -------------------------------------------------------------------------------- 1 | # Libretroを用いた開発について 2 | 3 | Libretro APIは、汎用的なオーディオ、ビデオ、入力のコールバックを公開する、軽量なCベースのアプリケーション・プログラミング・インターフェース(API)です。 4 | 5 | ## フロントエンド と コア 6 | 7 | Libretroの開発にあたって、考慮すべき2つの側面があります。 8 | 9 | - フロントエンド: libretro互換のコアを動作させることができるプログラムです。 10 | - コア: ゲーム、エミュレータ、メディアプレイヤーなどのプログラムで、libretroのフロントエンドで実行できるようにlibretro APIを実装したものです。 11 | 12 | スタンドアロンゲーム、ゲームエミュレータ、メディアプレイヤーなどのコアの開発者は、Direct3DやOpenGL用の異なるビデオドライバを書いたり、可能なすべての入力API、サウンドAPI、ゲームパッドなどに対応することを心配する必要はありません。 13 | 14 | コアは1つのライブラリファイルとして構築され、libretro APIをサポートするあらゆるフロントエンドで実行することができます。フロントエンドの責任は、すべての実装固有の詳細を提供することです。コアの役割はメインプログラムを提供することだけです。 15 | 16 | ## `libretro.h` 17 | 18 | libretro APIは、RetroArchのソースパッケージに含まれる`libretro.h`に記載されているいくつかの関数で構成されています。 19 | 20 | APIのヘッダはC99とC++に対応しています。C99では、bool型と``が使われています。このファイルの最新版は [`libretro-common`](https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h) にあります。 21 | 22 | ## コア開発に便利なリソース 23 | 24 | libretroの[githubリポジトリ](http://github.com/libretro/)の一部として管理されているコアの部分的なリストは `For Users > Core Documentation`セクション で見ることができます。 25 | 26 | コア開発の概要については[こちら](cores/developing-cores.md)を参照してください。 27 | 28 | ## フロントエンド開発に便利なリソース 29 | 30 | libretroのフロントエンドの数は増え続けており、様々なホストシステムやユースケースに対応しています。 31 | 32 | RetroArch は libretro のフロントエンドの参考例と言っていいほどの完成度で、さまざまなホストプラットフォームで利用できます。RetroARchの開発については、`For Developers > RetroArch Development`のセクションで詳しく説明しています。 33 | 34 | ## Libretro上で動作するOS 35 | 36 | LibreELECをベースにした[`Lakka`](http://www.lakka.tv/)は、Libretroを使ったOSディストリビューションのいいリファレンスと言えるでしょう。 37 | 38 | また、以下は、バックエンド技術の一部として libretro または RetroArch を使用している外部ディストリビューションの一部です。 39 | 40 | - [batocera.linux](http://batocera-linux.xorhub.com/) 41 | - [RetroPie](http://retropie.org.uk/) 42 | - [Recalbox](http://recalbox.com/) 43 | 44 | 注意: Libretroは、ライセンス料や縛りがなく、100%無料で実装できるオープンな仕様となっています。私たちのリファレンスフロントエンドは RetroArch です。この2つのプロジェクトは同じものではなく、これはライセンスに反映されています。RetroArch は GPLv3 でライセンスされていますが、libretro API は MIT ライセンスの API です。 45 | 46 | -------------------------------------------------------------------------------- /libretro/misc/core_info.md: -------------------------------------------------------------------------------- 1 | # Core info 2 | 3 | https://github.com/libretro/libretro-core-info/blob/c4c5b5d8fc094239c7f52f095d99f2dbff2bec03/00_example_libretro.info 4 | 5 | ```yaml 6 | # すべてのパラメータは任意ですが、ユーザー体験の向上に役立ちます。 7 | 8 | # Software Information - Information about the core software itself 9 | 10 | # ユーザーがコアを選択する際に表示される名前 11 | display_name = "Nintendo - Game Boy (Core Name)" 12 | 13 | # コアが属するカテゴリ(オプション) 14 | categories = "Emulator" 15 | 16 | # コアを書いた著者の名前 17 | authors = "Martin Freij|R. Belmont|R. Danbrook" 18 | 19 | # コアの名前(内部用) 20 | corename = "Nestopia" 21 | 22 | # コアがサポートする拡張子のリスト 23 | supported_extensions = "nes|fds" 24 | 25 | # コアのソースコードのライセンス 26 | license = "GPLv3" 27 | 28 | # コアを使用するために必要なプライバシー固有のパーミッション 29 | permissions = "" 30 | 31 | # コアのバージョン 32 | display_version = "v0.2.97.30" 33 | 34 | # Hardware Information - Information about the hardware the core supports (when applicable) 35 | 36 | # エミュレートされたシステムを製造したメーカー名 37 | manufacturer = "Nintendo" 38 | 39 | # コアが対象とするシステムの名称(任意) 40 | systemname = "Nintendo Entertainment System" 41 | 42 | # コアが使用する主なプラットフォームのID。可能であれば、他のコア情報ファイルを参考にしてください。 43 | # 空白または未使用の場合は、標準のコアプラットフォームが使用されます。 44 | systemid = "nes" 45 | 46 | # コアが必要とする 必須 or 任意 のファームウェアファイルの数です。 47 | firmware_count = 7 48 | 49 | # Firmware entries should be named from 0 50 | # Firmware description 51 | firmware0_desc = "filename (Description)" # ex: firmware0_desc = "bios.gg (GameGear BIOS)" 52 | # Firmware path 53 | firmware0_path = "filename.ext" # ex: firmware0_path = "bios.gg" 54 | # Is firmware optional or not, if not defined RetroArch will assume it is required 55 | firmware0_opt = "true/false" 56 | 57 | # 追加の注意事項: 58 | notes = "(!) hash|(!) game rom|(^) continue|[1] notes|[^] continue|[*] list" 59 | 60 | # Libretro Features - コアがサポートしているlibretro APIの機能です。コアのソートに便利です。 61 | 62 | # コアがセーブステートをサポートしているか 63 | savestate = "true" 64 | # `savestate`が`true`の場合に、セーブステート対応の完成度を表します。 basic, serialized (巻き戻しに必要), deterministic (ネットプレイ/先取りに必要) 65 | savestate_features = "serialized" 66 | # コアが libretro チートインターフェイスをサポートしていますか? 67 | cheats = "false" 68 | # コアが libretro の Input descripterをサポートしているか 69 | input_descriptors = "true" 70 | # コアが libretro の Memory descripterをサポートしているか(アチーブメントで利用) 71 | memory_descriptors = "false" 72 | # コアは libretro save インターフェースを使用しているのか、それとも独自のものを使用しているのかどうか。 73 | libretro_saves = "true" 74 | # コアオプションのインターフェースをサポートしていますか? 75 | core_options = "true" 76 | # コアオプションの対応バージョンは?(新しめのバージョンなら localization と descriptions に対応) 77 | core_options_version = "1.0" 78 | # コアがサブシステム(SGBのゲームボーイ)のインターフェースをサポートしているか 79 | load_subsystem = "false" 80 | # コアが動作するために外部ファイルを必要とするかどうか。 81 | supports_no_game = "false" 82 | # コアがサポートするデータベースの名前(任意) 83 | database = "Nintendo - Nintendo Entertainment System|Nintendo - Famicom Disk System" 84 | # フロントエンドでの`libretro-gl`やその他のハードウェアアクセラレーションのサポートを許可しているかどうか。 85 | hw_render = "false" 86 | # コアがサポートしているハードウェアレンダリングのAPIは?パイプ文字(|)で区切られています。 87 | required_hw_api = "Vulkan >= 1.0 | Direct3D >= 10.0 | OpenGL Core >= 3.3 | OpenGL ES >= 3.0" 88 | # 読み込み後、コアがファイルへの継続的なアクセスを必要とするか? 主にソフトパッチやデータのストリーミングに使用されます。 89 | needs_fullpath = "false" 90 | # コアは、その場でディスクを交換するためのlibretroディスク制御インターフェースをサポートしていますか? 91 | disk_control = "false" 92 | # そのコアは現在、一般的な使用に適していますか? つまり、一般ユーザーが便利に使えるのか、それとも開発専用なのか? 93 | is_experimental = "true" 94 | 95 | # 説明用テキストで、ツールチップに便利です。 96 | description = "This is a brief description of the core. It should be informative, but probably not super-long. 1024 characters, tops, all on one line (i.e., no manual line-breaks)." 97 | ``` 98 | -------------------------------------------------------------------------------- /libretro/retroarch/netplay.md: -------------------------------------------------------------------------------- 1 | # Netplay(WIP) 2 | 3 |
  4 | Note:
  5 | 
  6 | Netplayは、同じエミュレーションコアと同じコンテンツを実行している複数のRetroArchインスタンスを連続的に同期させることで、インターネット上で複数人プレイをエミュレートするRetroArchの機能です。
  7 | 
  8 | RetroArchのNetplayは、Nintendo64のような1つのゲーム機に複数人が集まってやるような複数人プレイにのみ対応しており、GBAのような複数のゲーム機を通信ケーブルでつないで行うような複数人プレイには対応していません。
  9 | 
10 | 11 | RetroArchでは、インターネット経由で2人以上のプレイヤーや観客を接続することができます。 12 | 13 | RetroArchのネットプレイコードはリプレイをベースにしており、デフォルト設定では入力遅延のない信頼性の低いネットワークでのネットプレイを実現しています。 14 | 15 | ネットプレイは最大16人のプレイヤーと任意数の観客に対応しています。 16 | 17 | RetroArchのネットプレイは、次の3つの細かい制約があるものの、完璧な同期を保って動作することが保証されています。 18 | 19 | 1. コアが`deterministic`であること。 20 | 2. コアが操作する入力デバイスは、ゲームパッドとアナログスティックだけであること。 21 | 3. コアとロードされたコンテンツの両方が、ホストとクライアントで同一なものであること。 22 | 23 | また、ネットプレイを適切に動作させるためには、コアがシリアライズに対応している必要があります。シリアライズに対応していないコアでは、ネットプレイは限定的にしか動作しません。シリアライズに対応することで、よりスムーズな体験ができるようになります。 24 | 25 | RetroArchのネットプレイは、**ネットワークからの遅延入力を期待し、その遅延入力を巻き戻して再生することで、一貫した状態を得る**という仕組みになっています。 26 | 27 | ある時点では、すべてのネットプレイクライアントは矛盾した状態にあるかもしれませんが、お互いに遅延データを受信すると、矛盾していた最後の時点に目に見えない形で巻き戻し、新しい入力でエミュレータを実行して、巻き戻す前の状態よりも正規の状態に近い新しい状態に遷移します。 28 | 29 | どのフレームがどのフレームであるかについて双方が合意している限り、それぞれの入力イベントが常に正しいフレームで起こるので、両者の同期がずれることはありません。 30 | 31 | ## 仕様 32 | 33 | ネットプレイのトランスポートプロトコルはTCPを使用しています。これは信頼性と順番通りのデータ転送が正しい動作のために必要だからです。 34 | 35 | 1台のネットプレイサーバが、複数のネットプレイクライアントからの接続を受けることがあります。 36 | 37 | ほとんどの場合、サーバとクライアントは同等の参加者ですが、グローバルな同期を必要とする操作では、サーバが正規の参加者となります。 38 | 39 | 通常の動作では、ゲームをプレイ中の各クライアントは、フレームごとに自分のコントローラの入力データを送信するだけであり、サーバはクライアント間で入力データを転送します。 40 | 41 | すべてのクライアントとサーバーは、どのプレイヤーのスロットが使用されているか(つまり、どのコントローラが接続されているか)を認識しており、サーバーはどのクライアントがどのプレイヤーのスロットに対応しているかを認識しています。 42 | 43 | すべてのプレイヤーがフレームごとに入力データを順番どおりに送信することが重要であるため、すべてのクライアントはすべてのプレイヤーのフレームカウンタを保持しています。 44 | 45 | 予想外に低いフレーム数の入力データを受信した場合は無視され、予想外に高いフレーム数の入力データを受信した場合は接続を終了します。 46 | 47 | また、すべてのプレイヤーがどのフレームがどのフレームであるかに同意することが重要です。 48 | 49 | そのため、最初の接続時に、サーバーは正規のフレームカウントとシリアライズされたセーブステートをクライアントに与え、クライアントがストリームの途中で参加できるようにします。観戦者は入力データを送ることはありません。 50 | 51 | プレイヤーから新しい入力データを受け取ったとき、それが現在実行中のフレームより前であれば、RetroArchは目立たないように巻き戻して新しい入力データを使って再実行し、元のフレームに戻ってくるので、ローカルプレイヤーの入力は常に無意識に行われます。 52 | 53 | その他のイベントは、サーバのフレームカウンタに連動しており、ほとんどのイベントはサーバのみが行うことができます。例えば、クライアントが観戦からプレイに切り替える場合、単に入力データの送信を開始することはできず、モード変更のリクエストをサーバーに送信する必要があります。このとき、クライアントは、以前のフレームのデータを送信するために巻き戻したり、ローカルのフレーム数がサーバのフレーム数に達するまで入力データの送信を待つ必要があるかもしれません。 54 | 55 | 特に、リセットやセーブステートのロードは、常にサーバのフレームカウントに同期しているため、サーバのみがコアのリセットやセーブステートのロードを行うことができます。 56 | 57 | セーブステートのロードの前の入力はロード後には必要ないので、セーブステートのロードコマンドを受信すると、すべてプレイヤーのフレームカウントは、少なくともサーバーのフレームカウント(該当する場合はローカルのフレームカウントを含む)に更新されます。これは、フレームカウントが、実行フレームあたり1フレーム以上の割合でスキップする唯一の条件です。 58 | 59 | ## 実装 60 | 61 | Netplayは実質的に、リングバッファとして実装されている入力データのバッファと、いくつかのプリフレームとポストフレームの挙動で構成されています。 62 | 63 | RetroArchのNetplayでは、`Self`, `Other`, `Unread` という、3つの重要なフレームのロケーションが存在します。 64 | 65 | それぞれのロケーションが、フレームと、そのフレームに対応するステートバッファを参照します。ステートバッファには、そのフレームのセーブステートと、ローカルとリモートの両方のプレーヤーからの入力が含まれます。 66 | 67 | `Self`はRetroArchが自分自身の位置を認識しているロケーションで、相手から読み取った位置よりも前か後ろかになります。`Self`は、ステートをロードするためにローカルのフレームカウントが強制的にスキップされる場合を除き、実行されたフレームごとに1フレームの割合で進行します。 68 | 69 | `Unread`とは、すべてのプレイヤーのデータが読み込まれていない最初のフレームのことです。通常、`Unread`は`Self`よりも小さいですが、あるクライアントが他のクライアントよりも 他のクライアントよりも先に進むことが可能です。 70 | 71 | `Other`は、直近で完全に同期していたロケーションのことです。つまり、`Other-1`は、ローカルとリモートのすべての入力が実行された最後のフレームを指します。同期をとるために`Other`よりも遠くに巻き戻す必要はなく、`Other`は常に`Self`と`Unread`の両方以下です。ステートバッファはリングなので、`Other`は上書きしてはいけない最初のフレームとなります。 72 | 73 | サーバーは、複数のクライアントを扱うことができるので、若干ですが複雑な仕事をする必要があります。プレイ中の各コネクションについて、プレイヤーごとの`Unread`フレームを維持し、各プレイヤーの`Unread`フレームのうち最も早いものをグローバル`Unread`フレームとします。 74 | 75 | また、サーバーは入力データを転送します。入力データがサーバの現在のフレームよりも前のフレームから受信された場合、サーバは直ちにそれを転送します。 76 | 77 | それ以外の場合は、そのフレームに達した時点で転送します。つまり、フレーム`n`の間、サーバーは自分のデータと他のプレイヤーのデータを任意の数だけフレーム`n`に送ることができますが、フレーム`n+1`を送ることはありません。これは、サーバーのクロックが、プレーヤーの反転、プレーヤーの加入・離脱、ステートの保存・読み込みなど、すべての同期関連イベントのアービターであるためです。 78 | 79 | TODO 80 | 81 | ## プロトコル 82 | 83 | ネットプレイの接続には、クライアントとサーバが同じソフトウェア(ROM)を使用していることを確認し、クライアントを同期させるためのハンドシェイクが必要です。その後、入力パケットの交換が行われます。 84 | 85 | **ハンドシェイクの手順** 86 | 87 | この部分はサーバーとクライアントの両方が行います。 88 | 89 | 1. コネクションヘッダの送信 90 | 2. コネクションヘッダを受信・検証 91 | 3. ニックネームの送信 92 | 4. ニックネームの受信 93 | 94 | クライアント側のみ 95 | 96 | 5. 必要ならばPASSWORDを送信 97 | 6. INFOを受信 98 | 7. INFOを送信 99 | 8. SYNCを受信 100 | 101 | サーバー側のみ 102 | 103 | 5. 必要ならばPASSWORDを受信 104 | 6. INFOを送信 105 | 7. INFOを受信 106 | 8. SYNCを送信 107 | 108 | なお、サーバとクライアントの両方が、コネクションヘッダを受け取る前に送信しています。これは意図的なものです。 109 | 110 | これにより、サーバとクライアントのどちらか(両方ではない)が、相手側のコネクションヘッダをエコーすることで、同じコネクションヘッダを使用することができます。 111 | 112 | ## その他の特徴 113 | 114 | 一般的には、入力レイテンシは望まれないと考えられています。しかし、入力レイテンシも選択肢の一つです。 115 | 116 | 入力レイテンシの利点は、実際の実行がフレーム数よりも遅れることです。 117 | 118 | フレーム数に比べて実際の実行が遅れるため、遠隔地のデータが利用できることが多くなり、巻き戻しの頻度もコストも少なくて済みます。 119 | 120 | ステートバッファには、入力遅延が有効な場合に使用される`run`というロケーションが追加されています。 121 | 122 | この場合、`self`は入力が読み込まれている場所を指し、`run`は実際に実行されているフレームを指します。`run`は純粋にローカルです。 123 | 124 | ## コマンドフォーマット 125 | 126 | Netplayコマンドは、32bitのコマンド識別子と、それに続く32bitのペイロードサイズ(いずれもネットワークのバイトオーダーに準拠)と、それに続くペイロードで構成されています。 127 | 128 | コマンド識別子は`netplay.h`に記載されており、以下にコマンドを説明します。特に指定のない限り、ペイロードの値はすべてネットワークのバイトオーダーに準拠です。 129 | 130 | **Command**: ACK 131 | 132 | Payload: None 133 | 134 | Description: 135 | > Acknowledgement. Not used. 136 | 137 | **Command**: NAK 138 | 139 | Payload: None 140 | 141 | Description: 142 | > Negative Acknowledgement. If received, the connection is terminated. Sent 143 | > whenever a command is malformed or otherwise not understood. 144 | 145 | **Command**: DISCONNECT 146 | 147 | Payload: None 148 | 149 | Description: 150 | > Gracefully disconnect. Not used. 151 | 152 | **Command**: INPUT 153 | 154 | Payload: 155 | 156 | { 157 | frame number: uint32 158 | is server data: 1 bit 159 | player: 31 bits 160 | controller input: uint32 161 | analog 1 input: uint32 162 | analog 2 input: uint32 163 | } 164 | 165 | Description: 166 | 167 | > Input state for each frame. Netplay must send an INPUT command for every 168 | > frame in order to function at all. Client's player value is ignored. Server 169 | > indicates which frames are its own input data because INPUT is a 170 | > synchronization point: No synchronization events from the given frame may 171 | > arrive after the server's input for the frame. 172 | 173 | **Command**: NOINPUT 174 | 175 | Payload: 176 | 177 | { 178 | frame number: uint32 179 | } 180 | 181 | Description: 182 | 183 | > Sent by the server to indicate a frame has passed when the server is not 184 | > otherwise sending data. 185 | 186 | **Command**: NICK 187 | 188 | Payload: 189 | 190 | { 191 | nickname: char[32] 192 | } 193 | 194 | Description: 195 | > Send nickname. Mandatory handshake command. 196 | 197 | **Command**: PASSWORD 198 | 199 | Payload: 200 | 201 | { 202 | password hash: char[64] 203 | } 204 | 205 | Description: 206 | > Send hashed password to server. Mandatory handshake command for clients if 207 | > the server demands a password. 208 | 209 | **Command**: INFO 210 | 211 | Payload: 212 | 213 | { 214 | core name: char[32] 215 | core version: char[32] 216 | content CRC: uint32 217 | } 218 | 219 | Description: 220 | > Send core/content info. Mandatory handshake command. Sent by server first, 221 | > then by client, and must match. Server may send INFO with no payload, in 222 | > which case the client sends its own info and expects the server to load the 223 | > appropriate core and content then send a new INFO command. If mutual 224 | > agreement cannot be achieved, the correct solution is to simply disconnect. 225 | 226 | **Command**: SYNC 227 | 228 | Payload: 229 | 230 | { 231 | frame number: uint32 232 | paused?: 1 bit 233 | connected players: 31 bits 234 | flip frame: uint32 235 | controller devices: uint32[16] 236 | client nick: char[32] 237 | sram: variable 238 | } 239 | 240 | Description: 241 | > Initial state synchronization. Mandatory handshake command from server to 242 | > client only. Connected players is a bitmap with the lowest bit being player 243 | > 0. Flip frame is 0 if players aren't flipped. Controller devices are the 244 | > devices plugged into each controller port, and 16 is really MAX_USERS. 245 | > Client is forced to have a different nick if multiple clients have the same 246 | > nick. 247 | 248 | **Command**: SPECTATE 249 | 250 | Payload: None 251 | 252 | Description: 253 | > Request to enter spectate mode. The client should immediately consider itself 254 | > to be in spectator mode and send no further input. 255 | 256 | **Command**: PLAY 257 | 258 | Payload: 259 | 260 | { 261 | reserved: 31 bits 262 | as slave?: 1 bit 263 | } 264 | 265 | Description: 266 | > Request to enter player mode. The client must wait for a MODE command before 267 | > sending input. Server may refuse or force slave connections, so the request 268 | > is not necessarily honored. Payload may be elided if zero. 269 | 270 | **Command**: MODE 271 | 272 | Payload: 273 | 274 | { 275 | frame number: uint32 276 | reserved: 13 bits 277 | slave: 1 bit 278 | playing: 1 bit 279 | you: 1 bit 280 | player number: uint16 281 | } 282 | 283 | Description: 284 | > Inform of a connection mode change (possibly of the receiving client). Only 285 | > server-to-client. Frame number is the first frame in which player data is 286 | > expected, or the first frame in which player data is not expected. In the 287 | > case of new players the frame number must be later than the last frame of the 288 | > server's own input that has been sent, and in the case of leaving players the 289 | > frame number must be later than the last frame of the relevant player's input 290 | > that has been transmitted. 291 | 292 | **Command**: MODE_REFUSED 293 | 294 | Payload: 295 | 296 | { 297 | reason: uint32 298 | } 299 | 300 | Description: 301 | > Inform a client that its request to change modes has been refused. 302 | 303 | **Command**: CRC 304 | 305 | Payload: 306 | 307 | { 308 | frame number: uint32 309 | hash: uint32 310 | } 311 | 312 | Description: 313 | > Informs the peer of the correct CRC hash for the specified frame. If the 314 | > receiver's hash doesn't match, they should send a REQUEST_SAVESTATE command. 315 | 316 | **Command**: REQUEST_SAVESTATE 317 | 318 | Payload: None 319 | 320 | Description: 321 | > Requests that the peer send a savestate. 322 | 323 | **Command**: LOAD_SAVESTATE 324 | Payload: 325 | 326 | { 327 | frame number: uint32 328 | uncompressed size: uint32 329 | serialized save state: blob (variable size) 330 | } 331 | 332 | Description: 333 | > Cause the other side to load a savestate, notionally one which the sending 334 | > side has also loaded. If both sides support zlib compression, the serialized 335 | > state is zlib compressed. Otherwise it is uncompressed. 336 | 337 | **Command**: PAUSE 338 | 339 | Payload: 340 | 341 | { 342 | nickname: char[32] 343 | } 344 | 345 | Description: 346 | > Indicates that the core is paused. The receiving peer should also pause. The 347 | > server should pass it on, using the known correct name rather than the 348 | > provided name. 349 | 350 | **Command**: RESUME 351 | 352 | Payload: None 353 | 354 | Description: 355 | > Indicates that the core is no longer paused. 356 | 357 | **Command**: STALL 358 | 359 | Payload: 360 | 361 | { 362 | frames: uint32 363 | } 364 | 365 | Description: 366 | > Request that a client stall for the given number of frames. 367 | 368 | **Command**: RESET 369 | 370 | Payload: 371 | 372 | { 373 | frame number: uint32 374 | } 375 | 376 | Description: 377 | > Indicate that the core was reset at the beginning of the given frame. 378 | 379 | **Command**: CHEATS 380 | 381 | Unused 382 | 383 | **Command**: FLIP_PLAYERS 384 | 385 | Payload: 386 | 387 | { 388 | frame number: uint32 389 | } 390 | 391 | Description: 392 | > Flip players at the requested frame. 393 | 394 | **Command**: CFG 395 | 396 | Unused 397 | 398 | **Command**: CFG_ACK 399 | 400 | Unused 401 | 402 | -------------------------------------------------------------------------------- /lle-vs-hle.md: -------------------------------------------------------------------------------- 1 | # LLE vs HLE 2 | 3 | >**Note** 4 | > この記事は [LLE vs HLE and their tradeoffs](https://alexaltea.github.io/blog/posts/2018-04-18-lle-vs-hle/) を翻訳したもので、主にエミュレータ開発者向けです。 5 | 6 | ## 前置き 7 | 8 | This article aims to give an intuitive understanding for the terms "Low-Level Emulation" (LLE) and "High-Level Emulation" (HLE) often heard in the emulation scene, their differences and tradeoffs in development/performance costs, and how developers choose one paradigm or the other. 9 | 10 | Machines are made of several layers of abstraction, each of them relying in the layer below to perform some particular task. In the context of gaming consoles, you might consider these layers (ordered from higher to lower level): 11 | 12 | - Game 13 | - Game engine 14 | - System libraries 15 | - Kernel/drivers 16 | - Hardware 17 | 18 | That's where these "low-level" or "high-level" terms come from. Something is more "high-level" when it has more layers of abstraction below it, and it's more "low-level" when it has more layers of abstraction above it. With so many layers, the terms "low" and "high" can become quite subjective (developers can't even agree about whether some emulators are HLE or LLE). Furthermore, you could go even below than hardware-level and start thinking about transistors, atoms, etc. as even deeper layers of abstraction. Similarly, there's also even higher levels like the game scripts that are sometimes used to handle events/dialogues in a game. Of course, for most emulators, these layers are either too low, or too high. Why? 19 | 20 | ## エミュレーションのパラダイム 21 | 22 | Let's tackle this question after giving an intuitive notion of what emulation is. Emulating a system all about putting a "barrier" between two adjacent layers of abstraction. For instance: 23 | 24 | **LLE emulators** 25 | 26 | e.g. EPSXE, PCSX2 27 | 28 | They put the barrier between the hardware and the kernel. The entire software stack would run as usual thinking it's on a real PS1, PS2 etc., but whenever the hardware is accessed (e.g. PCI configuration registers, MMIO accesses, etc.) the emulator would intercept that and execute whatever the emudevs wanted. This is the reason why you get the original console menus and the overall "look and feel" of the console. 29 | 30 | **HLE emulators** 31 | 32 | e.g. RPCS3, Citra 33 | 34 | They put the barrier between the kernel and userland (i.e. applications, games, etc.). The application runs as usual (of course, after translating userland instructions), but whenever it needs to access the operating system (e.g. to open files, to map memory, to create threads), that request aka. syscall will be intercepted and handled by some code written by the emudevs. This is the reason why you can typically just drag-and-drop a game and start playing it without booting any underlying OS. 35 | 36 | Back to the original question, why do emulators pick the barriers always at these two "hot spots", i.e. LLE (hardware and kernel) and HLE (kernel and userland)? 37 | 38 | When you place this "emulation" barrier between two layers, you have to reimplement the layer below (i.e. reimplement the hardware on LLE, reimplement the kernel on HLE), so that the layer(s) above it can execute successfully. This results in two costs that you have to balance: "development time" and "execution time". Let me explain why this balance is important with few extreme examples of poor balances: 39 | 40 | **Too high-levels** 41 | 42 | What would happen if you'd put that barrier between the game engine and the actual game? This idea used to be not so crazy, as it's what https://www.scummvm.org/ does. However, game engines these days are insanely complex with several million lines of code, it would take you centuries as a single developer to write an emulator that operated at such high levels. The "development time" would be massive, but the "execution time" (i.e. the emulator's performance) would be pretty good, since all the complex tasks have been reimplemented natively for the host system. 43 | 44 | **Too low-levels** 45 | 46 | What would happen if you wrote a transistor-level emulator? Again, not so crazy for old platforms, see the http://www.visual6502.org/ project. Assuming you had the equipment to decap a chip, a scanning electron microscope and fancy computer vision algorithms, you could easily generate code that simulates your target microprocessor, so little "development time", however, the "execution time" would be insanely high caused by simulating billions of transistors. 47 | 48 | As you see, the rule of thumb is: higher-level incurs in larger development costs, and lower-level incurs in larger execution costs. But this is not always the case, and it has frequently led to misconceptions among the end-users. One of them is wrongly estimating the perfomance of different emulator paradigms. 49 | 50 | ## パフォーマンスについての神話 51 | 52 | Let's debunk some of those performance myths: Assume you want to emulate some machine, and you are learning about its hardware/software to balence "development time" vs "execution time" and pick the right strategy. How do you estimate those costs, specially "execution time", aside from the naive rule of thumb above? Estimating how fast something will run isn't just about which levels of abstraction you are targetting. The resulting performance will be depend on how many "concepts" from your guest machine (i.e. the thing you're trying to emulate), can be mapped into your host machine (the thing that will run the emulator). 53 | 54 | To give you an example, one such "concept" is the MMU. To explain it briefly (and slightly wrong/oversimplified but for the sake of the explanation will do), the MMU is the thing that allows each application have access to a slice of RAM by mapping addresses of a "virtual address space" (an imaginary arrangement of memory) to a "physical address space" (the actual RAM). Every time the application accesses the memory with some CPU instruction, behind the scenes the MMU will translate the virtual address given by the application into a physical one. 55 | 56 | - HLE emulators typically don't worry about the guest MMU since guest applications only use virtual addressing and whenever they try to contact the guest kernel (e.g. to allocate more memory), the emulator takes control and very generously gives the guest application a chunk of its own host virtual memory. So everyone's happy. 57 | 58 | - LLE emulators have to worry about both the guest virtual memory and the guest physical memory. Many of them allocate guest physical memory during initialization, and do the "guest virtual memory to guest physical memory" translation by emulating the MMU on software. That causes every memory access (1 instruction) to invoke some specialized code that does the translation+access (100's of instructions). Of course, some translations can be cached, but the performance hit is still high. Remember that for every guest access, you have to traverse 4 layers: 59 | 1. Guest virtual memory 60 | 2. Guest physical memory 61 | 3. Host virtual memory 62 | 4. Host physical memory 63 | 64 | However in some scenarios (this depends on MMU quirks, page sizes, etc.), you could have use your host computer's own MMU to handle the accesses of the guest applications directly. One way of accomplishing this is running the guest software in a VM and having an hypervisor letting it directly access a slice of the host computer's physical RAM directly. This would remove the need for expensive software-based address translation and result in large performance gains. 65 | 66 | ## まとめ 67 | 68 | By making a better use of the host machine's resources, in the MMU and many other different areas, you can make even low-level emulation happen with an acceptable performance. It's not a surprise that Sony used this strategy to emulate the PS2 on the PS3, and Microsoft to emulate the Xbox on Xbox 360 and Xbox 360 on Xbox One. This 10x performance slowdown while doing LLE is a myth, resulting from many oversimplifications and/or people that have poorly utilized the host machine's resources. 69 | 70 | Of course, massive slowdowns can still happen: with really heterogeneous architectures, some concepts can be hard to map into each other and you might have to resort to software emulation incurring in 10x and 100x performance penalties, but this isn't always necessarily the case. There are no magic "performance penalty" numbers, everything has to be considered in a case-by-case basis, and the only way of estimating what that would be is getting to know both guest and host systems really in detail. -------------------------------------------------------------------------------- /near/README.md: -------------------------------------------------------------------------------- 1 | # Near's Respite 2 | 3 | https://near.sh/articles 4 | 5 | ## CPU 6 | 7 | - [ALU](cpu/alu.md) 8 | 9 | ## グラフィック 10 | 11 | - [色調エミュレーション](video/color-emulation/README.md) 12 | 13 | ## オーディオ 14 | 15 | - [DRC - Dynamic Rate Control](audio/dynamic-rate-control.md) 16 | 17 | ## 入力 18 | 19 | - [入力遅延](input/latency.md) 20 | - [先読み](input/run-ahead/README.md) 21 | 22 | ## 設計 23 | 24 | - [協調型スレッディング](design/cooperative-threading.md) 25 | - [スケジューラ](design/schedulers.md) 26 | - [階層構造](design/hierarchy/README.md) 27 | 28 | ## アドバイス 29 | 30 | - [巨人の肩に乗ろう](advice/shoulders-of-giants.md) 31 | -------------------------------------------------------------------------------- /near/advice/shoulders-of-giants.md: -------------------------------------------------------------------------------- 1 | # 巨人の肩に乗ろう 2 | 3 | この記事に書かれていることは、エミュレータ開発だけでなく、もっと幅広い分野に当てはまると思いますが、それでも私はこの記事をそのような観点から紹介したいと思います。 4 | 5 | 新しいエミュレータのプロジェクトを始めようとするとき、何から始めればいいのか、非常に悩むことがあります。他のエミュレータのソースコードを見てもいいのだろうか? 様々なディテールの実装方法を説明したノートを読んでもいいのでしょうか? それとも、ただのコピーなのでしょうか? などと疑問に思う箇所はたくさんあります。 6 | 7 | また、どうすればエミュレータ開発シーンに良い影響を与えることができるのでしょうか? 8 | 9 | ## 筆者の意見 10 | 11 | 2004年にbsnesを始めたとき、私はゼロから始めたわけではありません。1996年から2004年の間にSNESのリバースエンジニアリングとエミュレーションを手伝ってくれた何十人もの才能ある人たちの知恵と研究を結集して始めたのです。そのときに私はZSNESやSnes9Xなどのソースコード、ドキュメント、フォーラムへの投稿、バグフィックスの変更履歴など、8年分の進捗状況を手にすることができました。 12 | 13 | (また、自分が手がけたスーファミゲームの翻訳パッチの開発を通じてリバースエンジニアリングの経験が6年分あったのも収穫でした。) 14 | 15 | bsnesの初期に遡って検索してみると、bsnesが初めての大きなエミュ開発プロジェクトだったにもかかわらず、わずか半年で彼らの一般的な精度や互換性のレベルに追いつくことができたのです。 16 | 17 | これが私の特別な才能を表しているというのは冗談ですが、私には多くの助けがありましたし、そのことを恥じてはいません。私は、先人たちを頼り、彼らの仕事を研究し、彼らに質問し、彼らの忍耐と援助から大いに恩恵を受けました。 18 | 19 | anomie氏やTRAC氏などがbsnesの実現に貢献してくれました。彼らのおかげで今の私があるのです。 20 | 21 | ## Success Begets Success 22 | 23 | 半年間の開発期間を経て、今度は私が最前線で技術的な改善を行う番になりました。 24 | 25 | スーファミのサイクルタイミングをより正確に解析するための新しい技術を開発したり、何百ものテストROMを書いて、無数のエッジケースや新しい動作を確認したりしました。 26 | 27 | 既存の知識に頼っていた私は、自分で新しい情報を発見することに移行しなければなりませんでした。 28 | 29 | そのためには、ハードウェアのセットアップと、スーファミ用のプログラムを書くための知識が必要です。 30 | 31 | 厳密にはROMハックをしていた頃から持っていたものですが、新しいエミュレータを作るのに必要なものではありませんでした。 32 | 33 | 最近では、主要なレトロシステムについては、オリジナルのハードウェアを所有していなくても、比較的高品質なエミュレータを作成できるだけの情報があります。 34 | 35 | 例えば、MiSTerのSNESエミュレーションコアは、スーパーファミコンを持っていない人がbsnesのソースコードだけを参考にして作ったものです。 36 | 37 | ## Returning the Favor 38 | 39 | bsnesは現在、15年前から積極的に開発を続けています。しかし、私は自分の原点を忘れたわけではありません。むしろ、恩返しをしたいと思っています。今日のSNES界では、私の貢献はほとんどすべての分野で見ることができます。Snes9X、SNESGT、Mesen-S、Super Ntなどの質問に答えたり、ソースコードを提供したりしています。 40 | 41 | 私のソースコードは常にオープンソースで、必要に応じて他のプロジェクトで使用するために再ライセンスしたこともあります。例えば、Snes9Xの非商用ライセンスのための私のAPUコアなどです。 42 | 43 | そして、私が以前のZSNESやSnes9Xにほんのわずかな時間で追いつけたように、2018年以降の最新のスーファミエミュレータもすぐに我々のレベルに追いつくことができました。 44 | 45 | スーパーNtはわずか9ヶ月で、Mesen-Sはわずか4ヶ月で作られました。 46 | 47 | ## Research 48 | 49 | 例えば、スーファミの『Speedy Gonzales』は悪名高いバグでした。15年ほど前から、どのエミュレータでもこのゲームを動かす方法がわからなくなっていました。ステージ6-2の途中で何の理由もなくデッドロックしてしまうのです。 50 | 51 | 私は2週間のうち、約80時間をこのゲームのリバースエンジニアリングに費やし、何が起こっているのかを理解しようとしました。 52 | 53 | 問題となるコードを回避する方法はいくつかありました。しかし、正解だった方法はたった1つだけでした。他の方法を実行すると、その問題はなおっても新たに多くのバグが生まれてしまう有様でした。誤って修正すると、将来的に別のゲームを壊してしまう可能性もあります。 54 | 55 | そのためには、他の可能性を排除するために、徹底的にテストを行い、確実に正しい方法を見つけることが必要でした。 56 | 57 | 最終的にたどり着いた結論は、「ゲームはマッピングされていないメモリアドレスから読み込んで、セットされることのないビットを待っていた」というものでした。しかし、偶然にも、何千ものスキャンラインの後、Hblank DMA転送が、そのビットがセットされた値をフェッチし、サイクルがうまく揃えば、その値はバス上に残り、ループ状態は終了しました。 58 | 59 | この現象が発見された後、私はその答えを他のSNESエミュレータ開発者に提供し、彼らは数秒でこの動作を実装することができました。 60 | 61 | これは、昔のSnes9Xの変更履歴や、問題のあるゲームに関するフォーラムの投稿を読んで得られたバグフィックスと同じです。 62 | 63 | ## Cheating? 64 | 65 | エミュレータ開発はテストを受けるようなもので、他のエミュレータはそのテストの回答キーを持っているようなものだと考えれば、そのように見えるかもしれません。また、ハードウェアのリバースエンジニアリングを学ぶことが目的であれば、過去に行われたことを学ばなくても構わないでしょう。 66 | 67 | しかし、もしあなたの目的がオリジナルのハードウェアの保存であるならば、それは決して「不正行為」ではありません。現実的に考えて、利用可能なリソースを活用しているだけなのです。 68 | 69 | この世の時間は限られていて、私たちが保存しようとしているハードウェアは、もう若くもなく、すぐに手に入るものでもありません。最終的には、動くオリジナルのハードウェアを手に入れて分析することはできなくなるでしょう。それは、すでにいくつかの非常に古い希少なシステムではそうなっていますし、最新のものでも[アタリ ジャガーCD](https://ja.wikipedia.org/wiki/Atari_Jaguar)のように法外に高価で壊れやすいものになっています。 70 | 71 | 車輪を再発明する必要はありません。今ある道具を利用して、さらに良い車輪を作る努力をしましょう。罪悪感があっても、それを社会に還元すれば、簡単に解消されます。 72 | 73 | ## 終わりに 74 | 75 | 私が望むのは、エミュレータ開発を競争ではなく、チームワークとして捉えてもらえるようになることです。誰が最初にやったとか、誰が一番うまくやったとか、そういうことではありません。それは、ゲームスタジオの作品が生き続けられるようにすることであり、ひ孫たちが望めば、ビデオゲームがどのように始まったかを振り返ることができるようにすることです。 76 | 77 | エミュレータ開発では、誰もが神でも王でもありません。誰も他の人よりも重要ではありません。私たちは皆、一丸となっているのです。 78 | 79 | 繰り返しになりますが、既存の知識に頼る必要はありません。自分の道を切り開くのは自由です。しかし、先人たちの肩の上に立つことを恥じるべきではありません。 80 | 81 | 読んでいただきありがとうございました。これが、皆さんが何かを始めるきっかけになれば幸いです。皆さんがどんな作品を作るのか、楽しみにしています。頑張ってください。そして、私たちはあなたのためにここにいることを忘れないでください。 82 | -------------------------------------------------------------------------------- /near/audio/dynamic-rate-control.md: -------------------------------------------------------------------------------- 1 | # DRC - Dynamic Rate Control 2 | 3 | PCゲームを普通にPC上で実行する場合、ビデオを簡単に垂直同期させたり、オーディオトラックを丸ごとメモリに入れて単独で再生したりすることができます。また、必要に応じて効果音を鳴らすこともできます。その結果、シームレスな体験ができるのです。 4 | 5 | しかし、これがエミュレーションとなると、かなり難しくなります。PCゲームとは異なり、オーディオトラック全体を事前に知ることができないため、エミュレーションで生成されたサンプルをストリーミングする必要があるからです。 6 | 7 | この記事では、その理由と、いくつかの解決策をご紹介します。 8 | 9 | ## サイクル精度 10 | 11 | レトロゲーム機のエミュレーションが難しいのは、ゲームの種類の規模があまりにも大きいためです。1台のゲーム機には何千ものタイトルがありますから、少しでもタイミングがずれる、つまりエミュレーションの実行速度が速すぎたり遅すぎたりすれば、ゲームによってはエミュレータがクラッシュしまうでしょう。 12 | 13 | 一部の例外を除いて、100%の互換性を得るためには、レトロゲームのエミュレータはサイクル精度的に精確でなければなりません。 14 | 15 | つまり、ビデオ周波数(1秒間にレンダリングされるフレーム数)とオーディオ周波数(1秒間に再生されるサンプル数)が、(少なくともエミュレータが観測できる範囲で)オリジナルのハードウェアと一致する必要があります。 16 | 17 | エミュレータがティアリングやスタッタリングのない滑らかな映像を生成し、音飛びやノイズ音のない滑らかなオーディオを生成するためには、周波数の比率が静的なものであれば完全に一致していなければなりません。残念ながら、実機の場合、周波数の比率は後述のように動的に変化します。 18 | 19 | > ティアリング: 1枚の画像の中に複数フレームの画像が描画されてしまい、映像が途中で左右にずれたように見える現象のことです。 20 | 21 | > スタッタリング: 俗に言う「カクツキ」のことです。映像が途中でカクッカクッと一瞬止まったようになる現象です。 22 | 23 | ## 発振器と周波数 24 | 25 | しかし、これらの周波数は単純な数字ではありません。よく技術資料の中に、「スーパーファミコンはNTSC方式で映像のリフレッシュレートが60Hz、音声のサンプリングレートが32KHz」などと記載されているものがあります。しかし、それは正確ではありません。 26 | 27 | 映像や音声の周波数は、その土台となる発振器の周波数に基づいています。発振器には、水晶時計やセラミック発振子などがあります。その違いについては後ほど掘り下げて説明します。 28 | 29 | (NTSC版の)SNESのCPU周波数は `(315/88)*6000000 Hz`、つまり `約21.477MHz` です。 30 | 31 | SNESのAPUの周波数は `32000*768 Hz`。つまり `約24.576MHz`です。 32 | 33 | CPUの周波数は、SNESのPPUがNTSCのブラウン管モニターに画像を表示するために必要な、NTSCカラーサブキャリアを基準としています。 34 | 35 | スーファミの1スキャンラインは1364クロックサイクルで、1フレームは262スキャンラインです。 36 | 37 | したがって、スーファミのビデオリフレッシュレートは次のように導き出されます。`(((315/88)*6000000)/1364)/262 Hz` つまり `60.098477561Hz` です。 38 | 39 | ここでは簡単にするために、NTSCのカラーサブキャリアシフトのための4Hzの欠落期間に関する1つの注意事項は無視します。 40 | 41 | スーファミの1サンプルは768クロックサイクルかかるので `24576000/768 Hz`、つまり`32000 Hz`です。 42 | 43 | ## 動的ビデオレート 44 | 45 | スーファミのリフレッシュレートだけは、それほど単純ではありません。インターレースを有効にすると、奇数フレームに余分なスキャンラインが挿入され、2フィールドに525本のスキャンラインが表示されます。 46 | 47 | つまり、ビデオリフレッシュレートは `((((315/88)*6000000)/1364)/525)*2 Hz`、つまり `59.9840042665 Hz` となります。 48 | 49 | つまり、2つのリフレッシュレートを気にしなければならないのかと思われるかもしれませんが、残念ながら現実はもっと複雑です。 50 | 51 | ゲームがインターレースのオンとオフを同じ時間内に常に切り替えることは、理由がない限り止められません。これにより、有効なビデオ周波数は`59.98Hz`から`60.09Hz`の範囲で動的に変化しうるということになります。 52 | 53 | ## 発振器の不正確さ 54 | 55 | 残念なことに、電気工学の世界では、完璧な回路というものは存在しないのが現実です。すべては不正確さという許容範囲内を持って成り立っています。 56 | 57 | スーファミのCPUが使用しているような水晶振動子の場合、わずかな許容範囲があります。そのため、上記のように正確に`21.477MHz`になるのではなく、実際の周波数は変動します。 58 | 59 | 発振器の経年変化や製造工程、さらには現在の温度によっても、発振器の周波数は微妙に変化します。ファミコン本体が動いていても、オシロをつないで出力周波数をモニターすると、時間とともに微妙に変動しているのがみて取れるでしょう。 60 | 61 | スーファミのAPUの発振器はセラミック発振子です。これは一般的に安価であると言われていますが、その分、精度は水晶より劣ります。つまり、ばらつきが大きいのです。 62 | 63 | 実際には、スーファミのAPUの発振器は、`32040*768Hz(24.607MHz)` に近いという観測結果があります。 64 | 65 | ## エミュレータを動かすホストコンピュータの不正確さ 66 | 67 | スーファミの周波数が正確ではないということは、PCの周波数も正確ではないということです。 68 | 69 | PCのモニターを60Hzに設定し、オーディオ出力を48KHzに設定しても、それは正確には得られません。60.1Hzと48.03KHzの間に近い周波数が得られるかもしれません。 70 | 71 | また、仮に測定したとしても、時間の経過とともに変動してしまいます。 72 | 73 | ## 同期 74 | 75 | システムをエミュレートする場合、一般的には、エミュレートされたシステムを元のハードウェアよりもはるかに速く動かすことができます。なので、どうにかして速度を制限しなければなりません。 76 | 77 | ### VSync(ビデオに対する同期) 78 | 79 | 1つの方法は、エミュレートされたシステムとホストシステムのビデオリフレッシュレートがほぼ同じであることに頼ることです。 80 | 81 | スーファミが60.09Hz、PCモニターが60Hzの場合、モニターのVBlank期間に同期させることで、スクロール部分でもティアリングやスタッタリングが発生せず、常にスムーズな映像を提供することができるのです。 82 | 83 | エミュレートされたシステムは、実際には0.15%遅い動作となりますが、このようなわずかな違いが観測されることはほとんどありません。 84 | 85 | ビデオサンプルとオーディオサンプルの比率が正確でない場合、次の2つのうちどちらかが起こります。 86 | 87 | ビデオの比率が高すぎると、オーディオバッファは空になるまでゆっくりと減っていきます。サウンドカードは、再生するためのオーディオサンプルを必要としますが、何も利用できません。 88 | 89 | 使用しているオーディオAPIによっては、オーディオバッファがサンプルバッファの先頭にループバックするか(DirectSoundなど)、サウンドドライバがサウンドカードに無音を送信するか(XAudio2など)のどちらかになります。後者の方が望ましいのですが、結果は同じで、オーディオがポップしてひどい音になります。これが起こると、バッファは回復し、音は再びクリアになります。しかし、それは一時的なものです。 90 | 91 | スーファミのリフレッシュレートとPCのリフレッシュレートが 60.09Hz と 60.00Hz でずれていると、だいたい10秒に1回、音声が飛ぶことになります。 92 | 93 | ビデオの比率が低すぎると、音声バッファが完全に埋まってしまいます。オーディオバッファが大きければ大きいほど、画面に表示されるものとスピーカーから聞こえるものとの間に、より多くの入力ラグが生じることになるからです。そのため、バッファの前の部分を書き換えるか、サンプルを完全に削除するかのどちらかを迫られます。後者の方が若干良いのですが、やはりその都度、大きな音飛びやノイズが発生します。 94 | 95 | ### ASync(オーディオに対する同期) 96 | 97 | 例えば、スーファミの音声が約32KHzで、PCのサウンドカードのネイティブレートが約48KHzだとします。この音声を単純なリサンプラーに通すと、2つの入力サンプルに対して3つの出力サンプル、つまり3:2の比率で生成することができます。 98 | 99 | エミュレーションを実行して、オーディオバッファを満たします。オーディオバッファが一杯になると、エミュレーションを停止し、スペースが空くまで待ってオーディオサンプルを追加します。 100 | 101 | この結果、オーディオは完全にクリアになりましたが、今度は逆にビデオに問題が発生します。 102 | 103 | オーディオの比率が高すぎると、VBlank期間が発生しますが、表示するフレームがありません。アクティブな表示中にもフレームをレンダリングすると、画面の下にティアリングバーが這うように表示されてしまいます。逆に、そのフレームをスキップすると、スクロールシーンで顕著なスタッタリングが発生します。 104 | 105 | オーディオ比率が低すぎると、次のVBlank期間に2つのフレームが用意されることになり、やはり早めに描画するか、フレームを落とすかしなければならず、結果としてティアリングやスタッタリングが発生します。 106 | 107 | ### VSync と ASync の 両方する場合 108 | 109 | 今までの問題点を見て、両方に同期させようと思うかもしれません。 110 | 111 | しかし、そうすると両方の問題を解決するどころか結果的に両方の問題を引き起こすことになります。 112 | 113 | ビデオはスタッタリングやティアリングが発生し、オーディオは音飛びやノイズが発生します。これは最悪の事態です。さらにビデオとオーディオのどちらかがエミュレーションをブロックすると、もう片方もストールしてしまいます。 114 | 115 | ## SRC - Static Rate Control 116 | 117 | 効果的な手法のひとつに、SRC(Static Rate Control)があります。このモードでは、ビデオとオーディオの両方に実際に同期することが可能です。 118 | 119 | エミュレートされたシステムとホストシステムのビデオとオーディオの両方の正確な発振器の周波数は変動することがわかっており、理想的なリサンプリングの比率を決定することはできません。しかし、ユーザーにスライダー(微調整用のUI)を提供することで、比率を微調整することができます。 120 | 121 | 例えば、エミュレータのオーディオリサンプラのスライダーで、オーディオの比率を`1.9:3`から`2.1:3`まで調整できるようにしたとします。 122 | 123 | このスライダーを動かすと、一方では映像の乱れが生じ、他方では音声の乱れが生じることになります。しかし、慎重に中間点を狙うことで、ごくまれにしかスタッターが発生しない状況にすることができます。 124 | 125 | 練習を重ねた結果、この方法では、10分程度の映像と音声の完全な同期を得られる代わりに、生じたスタッタリングは20msとごくわずかでした。 126 | 127 | しかし、これでもまだ完璧ではありませんし、エミュレータを使用しているエンドユーザーに上記のことを説明するのは非常に困難です。 128 | 129 | ## DRC - Dynamic Rate Control 130 | 131 | しかし、オーディオのリサンプリング比率をリアルタイムに調整できるとしたらどうでしょうか? そう、それがDRC(Dynamic Rate Control)の要点です。 132 | 133 | このモードでは、映像にのみ同期します。DRCの目的は、オーディオバッファの容量を常に半分程度に保つことです。 134 | 135 | オーディオAPIを利用して、バッファに残っているサンプル数や、逆にバッファに残っている空き容量を問い合わせることで、いくつかのサンプルが出力されるたびにバッファの状態を確認することができます。 136 | 137 | バッファが空になってきたら、オーディオの比率を下げて、より多くのサンプルが生成されるようにすると、バッファが再び満たされてきます。しかし、これではどうしても行き過ぎてしまい、バッファが半分以上になってしまいます。そこで、逆に比率を上げてバッファが減り始めるように補正します。 138 | 139 | このようにオーディオリサンプラの比率を常に微調整することで、バッファが満杯にならず、空にならない状態を維持しています。 140 | 141 | 上記のように、スーファミが60.09Hzで動作するように設定されていて、PCモニターが60Hzで動作している場合、エミュレーションの動作が0.15%遅くなりますが、少なくとも映像と音声は常に完全に同期させることができます。 142 | 143 | ## Pitch Distortion 144 | 145 | オーディオのリサンプリング比を調整することで、実際にピッチを変化させます。 146 | 147 | そのため、1つのステップでピッチを調整しすぎないようにすることが非常に重要で、そうしないとオーディオは非常に不愉快な音声を出力することになります。 148 | 149 | ## 実装 150 | 151 | DRCは、理解するのに難しい概念ではありませんが、実装するのは少し難しいので、これからいくつかのコードを使って説明します。 152 | 153 | ここではコードを若干省略していますが、完全なソースコードは[私のGitHubリポジトリ](https://github.com/bsnes-emu/bsnes)でご覧いただけます。 154 | 155 | この記事のコードはパブリックドメインと考えてください。このコードは、浮動小数点の入力サンプルを想定しており、16bitの符号付き出力サンプルを生成しますが、これは私のエミュレータでの設計上の選択です。あなたのニーズに合わせてコードを変更してください。 156 | 157 | ### Cubic Resampler 158 | 159 | まず必要なのは、入力と出力の両方の周波数を受け持つリサンプラで、前に話した 2:3のリサンプリング比を実現するためのものです。 160 | 161 | オーディオDSPについては後ほど詳しく説明しますが、ここではシンプルな3次関数を使ったリサンプラを紹介します。このコードはどちらかというと標準的な補間ですので、ここではその動作を詳しく説明することはあまり重要ではありません。 162 | 163 | ```c++ 164 | auto Cubic::reset(double inputFrequency, double outputFrequency, uint queueSize) -> void { 165 | this->inputFrequency = inputFrequency; 166 | this->outputFrequency = outputFrequency ? outputFrequency : this->inputFrequency; 167 | 168 | ratio = inputFrequency / outputFrequency; 169 | fraction = 0.0; 170 | for(auto& sample : history) sample = 0.0; 171 | samples.resize(queueSize ? queueSize : this->outputFrequency * 0.02); // default to 20ms max queue size 172 | } 173 | 174 | auto Cubic::setInputFrequency(double inputFrequency) -> void { 175 | this->inputFrequency = inputFrequency; 176 | ratio = inputFrequency / outputFrequency; 177 | } 178 | 179 | auto Cubic::pending() const -> bool { 180 | return samples.pending(); 181 | } 182 | 183 | auto Cubic::read() -> double { 184 | return samples.read(); 185 | } 186 | 187 | auto Cubic::write(double sample) -> void { 188 | auto& mu = fraction; 189 | auto& s = history; 190 | 191 | s[0] = s[1]; 192 | s[1] = s[2]; 193 | s[2] = s[3]; 194 | s[3] = sample; 195 | 196 | while(mu <= 1.0) { 197 | double A = s[3] - s[2] - s[0] + s[1]; 198 | double B = s[0] - s[1] - A; 199 | double C = s[2] - s[0]; 200 | double D = s[1]; 201 | 202 | samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D); 203 | mu += ratio; 204 | } 205 | 206 | mu -= 1.0; 207 | } 208 | ``` 209 | 210 | ### Dynamic Rate Controller 211 | 212 | ここでは、いくつかのオーディオAPI(waveOut、DirectSound、OSSなど)をサポートしたコードを紹介したいと考えています。 213 | 214 | リサンプリング比率を制御する基本的な方法は、ベースクラスに抽象化することができます。まずはそのベースクラスについてみていきましょう。 215 | 216 | ```c++ 217 | auto Audio::output(const double samples[]) -> void { 218 | if(!instance->dynamic) return instance->output(samples); 219 | 220 | auto maxDelta = 0.005; 221 | double fillLevel = instance->level(); 222 | double dynamicFrequency = ((1.0 - maxDelta) + 2.0 * fillLevel * maxDelta) * instance->frequency; 223 | for(auto& resampler : resamplers) { 224 | resampler.setInputFrequency(dynamicFrequency); 225 | resampler.write(*samples++); 226 | } 227 | 228 | while(resamplers.first().pending()) { 229 | double samples[instance->channels]; 230 | for(uint n : range(instance->channels)) samples[n] = resamplers[n].read(); 231 | instance->output(samples); 232 | } 233 | } 234 | ``` 235 | 236 | ここでは,エミュレーション内で生成されたフレーム(サンプルのペア)ごとに `output()` を呼び出しています. `maxDelta` はピッチの最大歪みを制御します. 237 | 238 | `instance->level()`を呼び出すことで、任意のオーディオドライバの現在のバッファの埋まり具合を問い合わせることができます。また、`instance->output()`を呼び出すと、実際にサンプルがドライバーに送られ、スピーカーに出力されます。 239 | 240 | #### OSS (\*nix) のサポート 241 | 242 | OSSは、*nixシステム用の伝統的なオーディオインターフェイスです。Linuxではどちらかというと非推奨ですが、エミュレーションレイヤを介して利用できます。また、BSDでもよく動作します。 243 | 244 | ```c++ 245 | auto AudioOSS::level() -> double override { 246 | audio_buf_info info; 247 | ioctl(_fd, SNDCTL_DSP_GETOSPACE, &info); 248 | return (double)(_bufferSize - info.bytes) / _bufferSize; 249 | } 250 | 251 | auto AudioOSS::output(const double samples[]) -> void override { 252 | for(uint n : range(self.channels)) { 253 | buffer.write(sclamp<16>(samples[n] * 32767.0)); 254 | if(buffer.full()) { 255 | write(_fd, buffer.data(), buffer.capacity()); 256 | buffer.flush(); 257 | } 258 | } 259 | } 260 | ``` 261 | 262 | ここでは,`SNDCTL_DSP_GETOSPACE`により,バッファのフィルポジションを決定します。 263 | 264 | #### waveOut (Windows) のサポート 265 | 266 | waveOutは、おそらくWindows上で最も古いオーディオAPIですが、プラットフォーム上のどのAPIよりも信頼性の高いバッファポーリングを行うことができます。 267 | 268 | ```c++ 269 | auto CALLBACK waveOutCallback(HWAVEOUT handle, UINT message, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) -> void { 270 | auto instance = (AudioWaveOut*)userData; 271 | if(instance->blockQueue > 0) InterlockedDecrement(&instance->blockQueue); 272 | } 273 | 274 | auto AudioWaveOut::level() -> double override { 275 | return (double)((blockQueue * frameCount) + frameIndex) / (blockCount * frameCount); 276 | } 277 | 278 | auto AudioWaveOut::output(const double samples[]) -> void override { 279 | uint16_t lsample = sclamp<16>(samples[0] * 32767.0); //ensure value is between -32768 and +32767 280 | uint16_t rsample = sclamp<16>(samples[1] * 32767.0); 281 | 282 | auto block = (uint32_t*)headers[blockIndex].lpData; 283 | block[frameIndex] = lsample << 0 | rsample << 16; 284 | 285 | if(++frameIndex >= frameCount) { 286 | frameIndex = 0; 287 | while(waveOutWrite(handle, &headers[blockIndex], sizeof(WAVEHDR)) == WAVERR_STILLPLAYING); 288 | InterlockedIncrement(&blockQueue); 289 | if(++blockIndex >= blockCount) { 290 | blockIndex = 0; 291 | } 292 | } 293 | } 294 | ``` 295 | 296 | waveOutのバッファフィルレベルを決定するには、バッファのキューイングシステムを利用します。いくつかの小さなブロックに相当するオーディオをプッシュすると、キューにまだいくつのブロックが残っているかを読み取ることができます。これはマルチスレッドなので、`InterlockedIncrement/Decrement`を使用する必要がありますが、難しいことではありません。 297 | 298 | 個々のサンプルではなくブロック全体をクエリするのはあまり正確ではないので、大きなブロックではなく小さなブロックをたくさん作ることで補っています。 299 | 300 | 正確な比率は、正直なところ実験によります。私の場合、DRCがうまく機能するには、32個の小さなブロックが十分なキューサイズであると感じています。 301 | 302 | ## Adaptive Sync 303 | 304 | 実は、さらに優れた技術があります。あるシステムをオリジナルの速度でエミュレートすることができ、レートコントロールを一切必要としない素晴らしいものです。 305 | 306 | これはAdaptive Syncと呼ばれるもので、ビデオカードが一定の比率でフレームを出力する代わりに、エミュレータが画面を更新するタイミングをコントロールするものです。 307 | 308 | Adaptive Syncモニターを持っている人は少なく、60Hz以上のモニターを持っている人はさらに少なく(75hzのBandai WonderSwanのようなシステムには必要です)、この技術はウィンドウモードではうまく動作しません(動作させることはできますが)。また、非常に低レイテンシのオーディオドライバ(WASAPI、JACKなど)を必要としますが、どこでも動作させることは非常に困難です。 309 | 310 | しかし、これには1つの利点があります。DRCは、エミュレートされたシステムとホストシステムのビデオのリフレッシュレートがほぼ同じである場合にのみ機能し、特にウィンドウモードでは、エミュレーターがPCモニターのリフレッシュレートを変更することは現実的ではありません。 311 | 312 | しかし、この記事はDRCに関するものです。Adaptive Syncについては、次回以降に詳しくご紹介します。 313 | 314 | 最後に言いたいのは、エミュレータの開発者は、すべてのベースをカバーするために、DRCとAdaptive Syncの両方を実装するように努力すべきだということです。 315 | 316 | -------------------------------------------------------------------------------- /near/cpu/alu.md: -------------------------------------------------------------------------------- 1 | # ALU 2 | 3 | この記事で紹介するのは、Z80系のCPUで使われる、足し算,引き算のオーバーフロー,キャリー,ハーフキャリーのフラグを,分岐を使わずに計算するアルゴリズムです。 4 | 5 | 結果の計算に余分なビットを必要としないように設計されているので、より大きなサイズの型にテンプレート化することができ、あるマシンが使用できる最大の型でも使用することができます。 6 | 7 | もちろん、これらをテンプレート化するには、オーバーフローとキャリーマスクのために、与えられた整数型の最上位ビットを抽出するポータブルな方法が必要になります。 8 | 9 | 事前準備として最上位bitのマスク変数が必要です。次のコードをみてください。 10 | 11 | ```c++ 12 | natural sign = (natural(0) - 1 >> 1) + 1; 13 | // natural == uint8: sign -> 0b1000_0000 14 | ``` 15 | 16 | 上のコードについて、型`natural`は`uint1`から`uintmax`までの任意の型のどれかです。これで最上位bitが1の整数を得ることができます。 17 | 18 | ## ADC: add with carry 19 | 20 | ```c++ 21 | auto adc(natural target, natural source, boolean carry) -> uint8 { 22 | natural result = target + source + carry; 23 | natural carries = target ^ source ^ result; 24 | natural overflow = (target ^ result) & (source ^ result); 25 | 26 | flag.overflow = overflow & sign; 27 | flag.carry = (carries ^ overflow) & sign; 28 | flag.halfCarry = carries & 16; //Z80 29 | 30 | return result; 31 | } 32 | ``` 33 | 34 | ## SBB: subtract with borrow 35 | 36 | ほとんどすべてのプロセッサは、減算をボローで実装しています。一部のCPUベンダーはこれを混同し、別の演算であるにもかかわらずSBC(subtract with carry)と名付けています。東芝のTLCS900Hがその例です。 37 | 38 | ```c++ 39 | auto sbb(natural target, natural source, boolean carry) -> uint8 { 40 | natural result = target - source - carry; 41 | natural carries = target ^ source ^ result; 42 | natural overflow = (target ^ result) & (source ^ target); 43 | 44 | flag.overflow = overflow & sign; 45 | flag.carry = (carries ^ overflow) & sign; 46 | flag.halfCarry = carries & 16; //Z80 47 | 48 | return result; 49 | } 50 | ``` 51 | 52 | **SBB from ADC** 53 | 54 | ```c++ 55 | auto sbb(natural target, natural source, boolean carry) -> uint8 { 56 | natural result = adc(target, ~source, !carry); 57 | 58 | flag.carry = !flag.carry; 59 | flag.halfCarry = !flag.halfCarry; 60 | 61 | return result; 62 | } 63 | ``` 64 | 65 | ## SBC: subtract with carry 66 | 67 | 6502シリーズのプロセッサ(MOS6502、リコー6502、HuC6280、WDC65816)では、減算時にBorrowではなくCarryを使用しています。 68 | 69 | ```c++ 70 | auto sbc(natural target, natural source, boolean carry) -> uint8 { 71 | natural result = target - source - !carry; 72 | natural carries = target ^ ~source ^ result; 73 | natural overflow = (target ^ result) & (source ^ target); 74 | 75 | flag.overflow = overflow & sign; 76 | flag.carry = (carries ^ overflow) & sign; 77 | flag.halfCarry = carries & 16; //Z80 78 | 79 | return result; 80 | } 81 | ``` 82 | 83 | **SBC from ADC** 84 | 85 | Carryを使用する理由は、次のもう1つの実装を見るとよくわかります。 86 | 87 | ```c++ 88 | auto sbc(natural target, natural source, boolean carry) -> uint8 { 89 | return adc(target, ~source, carry); 90 | } 91 | ``` 92 | 93 | 6502はトランジスタ数が3500と少ないため、SBCのADC回路を再利用できるのは有利です。SBBでも同じことができますが、フラグを反転させる必要があるため、より複雑になります。 94 | 95 | ## 謝辞 96 | 97 | 分岐のないキャリー計算を実現するためのヒントを与えてくれたTalarubi氏に感謝します。 98 | -------------------------------------------------------------------------------- /near/design/cooperative-serialization.md: -------------------------------------------------------------------------------- 1 | # 協調型シリアライズ 2 | 3 | [前回の協調型スレッディングに関する記事](cooperative-threading.md)では、コンポーネントを協調スレッドとしてエミュレートすることの利点について説明しました。 4 | 5 | このアプローチの最大の問題点は、シリアル化(シリアライズ)、つまりエミュレーションで言うところのセーブステートです。 6 | 7 | 今回の記事では、この問題をより詳細に検討し、解決策を提案します。 8 | 9 | ## Recap 10 | 11 | シリアル化が難しいのは、関数内での位置関係を維持するステートマシン変数が、各スレッドが使用するネイティブスタックに移動しているからです。 12 | 13 | このスタックデータは極めて移植性が低く、仮にディスクからスタックの保存と読み込みを行ったとしても、プログラムの複数回の実行で特定の予約メモリアドレスを取得できる保証はありません。また、仮にできたとしても、ASLRの意味が薄れてしまいます。 14 | 15 | しかし、使える方法はいくつかあり、実際には、1つの方法だけではなく、それぞれの方法を組み合わせて使うのが最も効果的な傾向にあります。 16 | 17 | ## 例 18 | 19 | この記事では、シンプルなベアボーンの協調型スレッドCPUコアを定義しましょう。 20 | 21 | ```c++ 22 | void CPU::main() { 23 | if(interruptPending) interrupt(); 24 | instruction(); 25 | } 26 | 27 | void CPU::instruction() { 28 | auto opcode = fetch(); 29 | if(opcode == 0xa9) return opcodeLDAconst(); 30 | } 31 | 32 | void CPU::opcodeLDAconst() { 33 | A = fetch(); 34 | N = A & 0x80; 35 | Z = Z == 0; 36 | } 37 | 38 | void CPU::fetch() { 39 | step(2); 40 | auto data = bus.read(PC++); 41 | step(4); 42 | return data; 43 | } 44 | 45 | void CPU::step(uint clocks) { 46 | apu.clock -= clocks; 47 | while(apu.clock < 0) scheduler.switch(apu.thread); 48 | } 49 | ``` 50 | 51 | この例では、CPUコアはスタックフレームの4つの関数を終了した後、APUコアに実行を譲っています。ここで、APUコアがしばらく動作した後、セーブステートを取得する必要があると仮定します。 52 | 53 | 残念ながら、今はCPUの命令を実行している最中です。単純にCPUスレッドを再作成すると、`CPU::main()`の開始時に実行を開始することになりますが、これは全く望んでいないことです。 54 | 55 | ## Thread Alignment 56 | 57 | 上記のコードを次のように変更することもできます。 58 | 59 | ```c++ 60 | void CPU::main() { 61 | scheduler.leave(Scheduler::Serialize); 62 | if(interruptPending) interrupt(); 63 | instruction(); 64 | } 65 | ``` 66 | 67 | つまり、すべてのスレッドがmain関数の開始時に終了するまで、エミュレーションを実行し続けるという考え方です。 68 | 69 | 問題は、エミュレータの複雑さが増すにつれて、これがすぐに崩れてしまうことです。スレッド数が2~3個しかない場合でも、エミュレータがすべてのスレッドが完全に整列した状態にならないことがすぐにわかります。 70 | 71 | ## Method #1: 高速同期 72 | 73 | 1つ目の方法は、シリアル化のためにすべてのスレッドを同期(整列)させようとしている間、スケジューラが他のスレッドに切り替わるのを単純に止めることです。 74 | 75 | ```c++ 76 | void CPU::step(uint clocks) { 77 | apu.clock -= clocks; 78 | while(apu.clock < 0 && !scheduler.synchronizing()) scheduler.switch(apu.thread); 79 | } 80 | ``` 81 | 82 | しかし、上のコードでは、CPUがAPUより先に進んでいても、`bus.read(PC)`を呼び出すことができ、APUはPCが指すアドレスに書き込むことができるため、決定論が破られています。言い換えれば、エミュレートされたコンポーネントが非同期になるということです。 83 | 84 | CPUコードがAPUと同期していないのは、せいぜいCPU命令1つの分です。しかし、そのわずかな量でも、最も繊細なゲームの一部を壊すことができるのです。 85 | 86 | ## アウトオブオーダー実行 87 | 88 | また、協調スレッドの最も優れた使用例の一つである「アウトオブオーダー実行」を使い始めると、より深刻な問題が発生します。 89 | 90 | 上記のコードでは、ステートマシンのように、時間が経過するたびにCPUからAPUへと常に切り替えています。 91 | 92 | しかし、CPUが変更できないROMや、APUがアクセスできないメモリ範囲から読み込んでいることがわかっていたらどうでしょう? つまり、APUはCPUがバスから読み込んだバイトを変更することはできません。その場合、上のコードを次のように変更することができます。 93 | 94 | ```c++ 95 | void CPU::fetch() { 96 | step(2); 97 | auto data = bus.read(PC++); 98 | step(4); 99 | return data; 100 | } 101 | 102 | void CPU::step(uint clocks) { 103 | apu.clock -= clocks; 104 | } 105 | 106 | uint8_t Bus::read(uint16_t address) { 107 | //this is ROM, it cannot be changed 108 | if(address < 0x8000) return rom.read(address); 109 | //this is internal RAM, the APU cannot access it 110 | if(address < 0xc000) return iram.read(address - 0x8000); 111 | //this is external RAM, the APU can modify it 112 | while(apu.clock < 0 && !scheduler.synchronizing()) scheduler.switch(apu.thread); 113 | } 114 | ``` 115 | 116 | これは、CPUがAPUと共有しているRAMから命令を実行する可能性が低いため、結果的に非常に大きなスピードアップとなります。しかし、これは、CPUがAPUと長い間同期していないため、CPUがAPUよりも数百または数千命令進んでいる可能性があることを意味します。 117 | 118 | (実際には、上記のコード例では不十分で、チップが通信しない場合にAPUがデッドロックするのを防ぐために、最終的にAPUに強制的に切り替えなければなりませんが、例示のためにここでは省略しています。) 119 | 120 | つまり、CPUに1回の読み込みを完了させると、まれにAPUよりも数千命令も先に読み込まれてしまうことがあり、これははるかに重大な同期違反となります。このように、方法1(高速同期)は、間違いなくいくつかのゲームを壊し始めるでしょう。 121 | 122 | いずれにしても、先に進む前に、高速なシリアル化がどのようなコードになるかを見てみましょう。 123 | 124 | ```c++ 125 | void System::run() { 126 | scheduler.setMode(Scheduler::Running); 127 | //resume whichever thread was last active at last scheduler.leave() call 128 | scheduler.switch(scheduler.active->thread); 129 | } 130 | 131 | void System::serializeFast() { 132 | scheduler.setMode(Scheduler::Synchronizing); 133 | scheduler.switch(cpu.thread); 134 | scheduler.switch(apu.thread); 135 | //all threads are now paused at the start of their main() functions. 136 | //we can safely serialize the system now and ignore their stacks. 137 | //when unserializing, we simply recreate new, empty stack frames. 138 | } 139 | ``` 140 | 141 | ## Method #2: 厳密同期 142 | 143 | TODO 144 | 145 | ## 決定論 146 | 147 | 厳密同期にはもう一つの問題があります。それは処理の完了までに潜在的に多くの時間がかかることです。 148 | 149 | システムをシリアル化する際、理想的には時間を全く進めたくありません。時間を進めると決定性が損なわれます。 150 | 151 | コントローラの入力をあらかじめ記録しておき、それを再現する場合を考えてみましょう。つまり、以前にプレイしたゲームを録画したムービーを再生しているのです。 152 | 153 | 1回目は普通にムービーを再生し、2回目は途中で状態を保存するように指示しました。この小さな動作が、もし高速同期法を使ってしまうと、ごくわずかな非同期化を引き起こし、ドミノ倒しのようにムービーの同期が失われてしまう可能性があります。 154 | 155 | リアルタイム巻き戻しのようなものを実装したい場合、巻き戻しのための履歴バッファを持つために、システムを常にシリアライズする必要があります。このような潜在的な非同期化は、どんどん増えていきます。 156 | 157 | ## Method #3: Hibernation (Unsynchronized) 158 | 159 | TODO 160 | 161 | ## Putting It All Together 162 | 163 | TODO 164 | 165 | ## 終わりに 166 | 167 | TODO 168 | 169 | -------------------------------------------------------------------------------- /near/design/cooperative-threading.md: -------------------------------------------------------------------------------- 1 | # 協調型スレッディング 2 | 3 |
  4 |   この記事は Cooperative Threading を翻訳したものです。
  5 |   独自の説明を加筆したり、意訳したものところもあります。
  6 | 
7 | 8 | エミュレータ実装の最大の課題は、プログラミングコードが本質的にシリアルであるのに対し、エミュレータでは多くのプロセスが並列に実行されていることです。 9 | 10 | 実際、レトロゲームのエミュレータを書く際に難しいのは、大体の場合、コンポーネント間の同期の管理です。 11 | 12 | この記事では、私がエミュレータのために選んだアプローチである協調型マルチスレッドについて説明します。これはいくつかのアプローチのうちの1つであり、私はこのアプローチが最善だという主張をしたいわけではありません。私は、このアプローチの長所と短所をなるべく客観的に紹介するつもりです。 13 | 14 | ## 同期の概要 15 | 16 | 今回の目的は、エミュレータのコンポーネント間の同期です。 17 | 18 | 例えば、スーパーファミコンのエミュレータを実装する際に、システムを 汎用CPU、オーディオAPU、ビデオPPU、サウンド出力DSP の4つのコンポーネントに分けるとします。ここでは、説明のために少し物事を単純化しています(例えば、PPUプロセッサは2つあります)。 19 | 20 | これらのコンポーネント(チップ)はそれぞれ約20MHzのクロックレートで動作します。 21 | 22 | エミュレータでは各チップをシリアルに、つまり1つずつ動作させる必要がありますが、本物のスーファミではこれらのチップをすべてパラレルに、つまり同時に動作させます。 23 | 24 | エミュレータでは1つのチップを一定時間動作させた後、そのチップに追いつくように他のチップを一定時間動作させて同期させます。 25 | 26 | エミュレーションの精度を高くしたいなら、この同期間隔をより短くする必要があります。 27 | 28 | 1つの実装例としては、一度に1つのCPUオペコードを実行し、一度に1つのスキャンラインをレンダリングし、一度に1つのオーディオサンプルを生成することができます。そうすれば、1990年代後半に開発されたものと同じくらいの精度の、非常に高速なスーファミのエミュレータができあがります。 29 | 30 | 他の実装例としては、CPUのオペコードサイクルを1つ分実行し、一度に1ピクセルのレンダリングを行い、オーディオサンプルの生成を32の異なるサイクルステージに分割することもできます。そうすると、`bsnes`のような高精度なエミュレータができます。 31 | 32 | エミュレーションの同期間隔が短くなるほど、コンポーネントから別のコンポーネントへの切り替えも複雑になります。例えば、CPUの命令をエミュレートしているときに、1サイクルごとにビデオPPUに切り替えて1ピクセルだけ描画し、再びCPUに戻る必要があるとします。これではエミュレータのコードがとても複雑になってしまいますが、これを解決する1つのアプローチが協調スレッド処理です。 33 | 34 | ## 先取り型スレッディング 35 | 36 | 最近のプロセッサは多くのコアを持っていますし、シングルコアのCPUでもOSのスレッド機能で複数のタスクをスレッドの形で並列に実行することができます。これを利用してエミュレータを作ることはできないのでしょうか? 37 | 38 | 答えは、残念ながらNO(できない)です。 39 | 40 | スーファミを正確にエミュレートするには、エミュレートされた1秒間に数千万回の同期(コンテキストスイッチ)が必要になります。 41 | 42 | CPUがシングルコアの場合、内部的にはすべてのスレッドはシリアルで実行され、OSはカーネルモードでコンテキストスイッチを実行します。ユーザーランドとカーネルモードの切り替えは、CPUにとって非常にコストが高く、どれだけ性能のいいCPUでも1秒間に数十万回程度です。 43 | 44 | CPUがマルチコアの場合、シングルコアよりは状況はマシと言えますが、そもそも、シングルコアでもマルチコアでも、PCのクロック(約3GHz)とスーファミのクロック(約20MHz)が異なるという事実に対処しなければなりません。 45 | 46 | 結局、各コンポーネント間の同期を取るには、ロックやミューテックス、セマフォなどを使って、あるコンポーネントのスレッドで一定時間実行した後に、そのスレッドが他のスレッドを待つようにしてやる必要があります。 47 | 48 | しかし、これらの方法も非常にコストが高く、インターロックインクリメントやインターロックデクリメントのようなアトミックな命令に落とし込んだとしても、現代のCPUは巨大なパイプラインと各CPUコア間の低速なデータ線で大規模な並列化を行っているため、それに対抗することはできません。 49 | 50 | ## ステートマシーン 51 | 52 | レトロゲームのエミュレータ開発では、ステートマシンを用いることが一般的です。 53 | 54 | 例としてサイクルベースのCPUインタプリタを、ステートマシンを使って分割する方法を紹介します。 55 | 56 | ```c++ 57 | // SNESをモデルとしていますが、説明のために大幅に簡略化しています。 58 | void CPU::executeInstructionCycle() { 59 | // cycle = CPUサイクル 60 | cycle++; 61 | if (cycle == 1) { 62 | opcode = readMemory(PC++); 63 | return; 64 | } 65 | 66 | // M=1: 8-bit accumulator instructions in SNES. 67 | if(FlagM) { 68 | switch(opcode) { 69 | // B9: MOV A,[nnnn+Y], 4 CPUサイクル 70 | case 0xb9: 71 | switch(cycle) { 72 | case 2: 73 | address = readMemory(PC++); 74 | return; 75 | 76 | case 3: 77 | address = readMemory(PC++) | address << 8; 78 | return; 79 | 80 | case 4: 81 | // possible penalty cycle when crossing 8-bit page boundaries: 82 | if(address >> 8 != address + Y >> 8) { 83 | return; 84 | } 85 | cycle++; // cycle 4 not needed; fall through to cycle 5 86 | 87 | case 5: 88 | A = readMemory(address + Y); 89 | cycle = 0; // end of instruction; start a new instruction next time 90 | return; 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | PPUによるレンダリングをピクセル単位のタイムスライスに分割したり、APUによるサンプル生成をサイクル単位のタイムスライスに分割したりと、それぞれの`cycle`に複雑な処理が必要です。 98 | 99 | スーファミの場合、これだけでは足りません。CPUの1サイクルは、6〜12のマスタークロックサイクルからなります。アドレスバスに目的のアドレスが準備され、一定時間後に他のチップがバス上のデータを確認するのにかかる時間のせいです。これを`bus hold delay`といいます。 100 | 101 | 基本的に、CPUがPPUからデータを読み出す場合、PPUが瞬時にデータを返すことは期待できません。また、CPUがPPUに書き込む場合にも少々時間が必要です。 102 | 103 | そのため、読み出しをクロックサイクル単位に分割するためには、上記の`readMemory()`を呼び出すたびに、さらに細かいレベルのステートマシンを追加する必要があります。 104 | 105 | ```c++ 106 | // SNESをモデルとしていますが、説明のために大幅に簡略化しています。 107 | void CPU::executeInstructionCycle() { 108 | // cycle = CPUサイクル 109 | if (cycle == 1) { 110 | opcode = readMemory(PC++); 111 | cycle = 2; 112 | return; 113 | } 114 | 115 | // M=1: 8-bit accumulator instructions in SNES. 116 | if (FlagM) { 117 | switch (opcode) { 118 | // B9: MOV A,[nnnn+Y], 4 CPUサイクル 119 | case 0xb9: 120 | switch (cycle) { 121 | case 2: 122 | switch (subcycle) { 123 | case 1: 124 | subcycle = 2; 125 | return; 126 | case 2: 127 | address = readMemory(PC++); 128 | subcycle = 1; 129 | return; 130 | } 131 | 132 | case 3: 133 | switch (subcycle) { 134 | case 1: 135 | subcycle = 2; 136 | return; 137 | case 2: 138 | address = readMemory(PC++) | address << 8; 139 | subcycle = 1; 140 | return; 141 | } 142 | 143 | case 4: 144 | // possible penalty cycle when crossing 8-bit page boundaries: 145 | if (address >> 8 != address + Y >> 8) { 146 | return; 147 | } 148 | cycle++; //cycle 4 not needed; fall through to cycle 5 149 | 150 | case 5: 151 | switch (subcycle) { 152 | case 1: 153 | subcycle = 2; 154 | return; 155 | case 2: 156 | A = readMemory(address + Y); 157 | subcycle = 1; 158 | cycle = 0; //end of instruction; start a new instruction next time 159 | return; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | ``` 166 | 167 | ## 協調型スレッディング 168 | 169 | 協調型スレッディングの考え方は先取り型スレッディングと似ていますが、カーネルがスレッドを処理するのではなく、ユーザーランドのプロセスが代わりに処理します。 170 | 171 | 協調スレッドはすべてシリアルで実行されます。つまり、スレッドは一度に1つしか実行されません。必要に応じてコンテキストスイッチで別のスレッドに切り替え、前回終了したところから再開することになります。 172 | 173 | コルーチンとは異なり、各協調スレッドは独自のスタックフレームを持ちます。これは、1つのスレッドが、スタックフレームの奥深くにある数層の関数呼び出しになり得ることを意味します。 174 | 175 | 上で見たように、ステートマシンのコードはとても記述量が多くなってしまいますが、協調型スレッドのアプローチに沿って書くとスタックフレームの一部として隠蔽することが可能です。 176 | 177 | 試しに、上のコードを書き換えると、次のようになります。 178 | 179 | ```c++ 180 | void CPU::executeInstruction() { 181 | opcode = readMemory(PC++); 182 | 183 | // M=1: 8-bit accumulator instructions in SNES. 184 | if(FlagM) { 185 | switch(opcode) { 186 | // B9: MOV A,[nnnn+Y], 4 CPUサイクル 187 | case 0xb9: 188 | address = readMemory(PC++); 189 | address = readMemory(PC++) | address << 8; 190 | if (address >> 8 != address + Y >> 8) wait(6); 191 | A = readMemory(address + Y); 192 | } 193 | } 194 | } 195 | ``` 196 | 197 | コードが一気にシンプルになりました。 198 | 199 | どのような仕組みになっているのでしょうか? そのためには、`readMemory()`関数の中を見てみましょう。 200 | 201 | ```c++ 202 | uint8_t CPU::readMemory(uint16_t address) { 203 | wait(2); 204 | uint8_t data = memory[address]; 205 | wait(4); 206 | return data; 207 | } 208 | ``` 209 | 210 | `wait()`の中も見てみましょう。 211 | 212 | ```c++ 213 | void CPU::wait(uint clockCycles) { 214 | apuCounter += clockCycles; 215 | while (apuCounter > 0) { 216 | yield(apu); 217 | } 218 | } 219 | ``` 220 | 221 | `wait()`はクロックサイクルを消費するだけですが、CPUがAPUよりも先に進んだ状態になると、CPUは別スレッドで実行されているAPUに実行を譲り、APUは前回の続きから再開することになります。 222 | 223 | APUが追いつき、CPUよりも先に進んだ時点で、APUはCPUに処理を譲り、CPUは最後の`yield()`した直後から再開します。 224 | 225 | 協調型スレッドは呼び出し(Call)よりもジャンプと考えることができます。APUでは、戻るのではなく、CPUにジャンプしただけです。(結果だけ見るとCallのように戻っていますが) 226 | 227 | 技術的には、これを対称型の協調スレッドと呼びます。 228 | 229 | 典型的な`call/return`のスタイルのように機能する非対称型の協調スレッドもありますが、私の考えでは、多くのスレッドがすべて並行して実行されるエミュレータには適していません。CPUがAPUにジャンプし、APUがCPUに戻るのではなくPPUにジャンプする場合などがあり、そのような場合に`call/return`スタイルでは対応しきれないからです。 230 | 231 | ## 協調型スレッディングの最適化 232 | 233 | ひとつ気になることがあります。それは、なぜクロックサイクルごとにCPUとAPUを同期させているのかということです。 234 | 235 | 例えば、CPUがAPUからアクセスできない内部メモリを読み取る場合、APUがその前に内部メモリの内容を変更する可能性は無いので同期を取る必要がありません。 236 | 237 | 基本的には、プロセッサ間で同期をとる場合は、エミュレートしたプロセッサを可能な限り小さなタイムスライスに分割し、それに合わせてステップを踏む必要があります。 238 | 239 | エミュレータは、CPUがあるサイクルでAPUにアクセスしようとしていないことを確認して、APUを少し動かす前にもっと多くのサイクルを走らせることができますが、実際にはステートマシンを通じてすでにオーバーヘッドを支払っていることになります。 240 | 241 | しかし、協調スレッドは、私が「jit同期」と呼ぶ最適化の機会を与えてくれます。先ほどの例をもう少し詳しく説明しましょう。 242 | 243 | ```c++ 244 | uint8_t CPU::readMemory(uint16_t address) { 245 | wait(2); 246 | if (address >= 0x2140 && address <= 0x2143) { 247 | // この読み出しは、APUとの共有メモリにアクセスすることになります。 248 | // つまり、CPUが読み出す前にAPUがその内容を変更している可能性があるので、APUに処理を渡してCPUに追いつかせる必要があります。 249 | while (apuCounter > 0) { 250 | yield(apu); 251 | } 252 | } 253 | 254 | uint8_t data = memory[address]; 255 | wait(4); 256 | return data; 257 | } 258 | ``` 259 | 260 | ```c++ 261 | void CPU::wait(uint clockCycles) { 262 | apuCounter += clockCycles; 263 | } 264 | ``` 265 | 266 | 上のコード例では、APUとの共有メモリ領域からの読み出しを試みるまで、CPUが動作し続けるようにしています。そして、共有メモリの場合は、APUがCPUに追いついてから読み出しを行うようにしました。 267 | 268 | CPUがAPUとの同期を頻繁に取る必要がなければ、あるいは逆にAPUがCPUとの同期を頻繁に取る必要があれば、1秒間のコンテキストスイッチの回数は劇的に減少します。 269 | 270 | 実際にスーファミ(`bsnes`)の場合、CPUとAPUを同期させる必要があるのは1秒間に数千回程度でした。 271 | 272 | ## 協調型スレッディングの限界 273 | 274 | しかし、2つのプロセッサ(ここではCPUとAPU)がお互いにすべてのメモリアドレス空間を共有している場合はどうでしょうか? 275 | 276 | そうすると、(現在先行している)CPUがメモリを読み出す前にAPUがメモリを変更する可能性があるため、常に同期を取る必要があります。 277 | 278 | 例えば、Nemesis社のExodusエミュレータのように、ロールバック機構を実装するなどの方法がありますが、その場合、このアプローチによるコードの簡素化の利点が失われてしまいます。 279 | 280 | 最悪の場合、協調スレッド処理は、ステートマシンと同じレベルのオーバーヘッドに戻ってしまいます。 281 | 282 | ## パイプラインのストール 283 | 284 | 実際には、最悪の場合、協調スレッド処理はステートマシン方式よりも多少遅くなります。 285 | 286 | 最近のCPUは、多段のパイプラインを構築することで高速化を実現しています。 287 | 288 | 協調スレッディングでスタックポインタを変更すると、このパイプラインがパニックに陥る可能性が高いです。 289 | 290 | ``` 291 | 例えば、組み立てラインがあるとします。ラインに沿って、製品がゆっくりと組み立てられていきます。 292 | ここで、順調に製品を組み立てている最中に、別の製品の注文が入り、待ちきれなくなったとします。しかし、組み立てラインは1本しかありませんでした。 293 | 組立ラインを止めて、すべての商品を外し、新しい商品の組立を始めなければなりません。1秒間に何千万回も、これを繰り返すことを想像すると、サイクル精度の高いレトロゲームのエミュレータがなぜこれほどまでにマシンパワーを要求するのかがわかってきます。 294 | ``` 295 | 296 | とはいえ、ステートマシンも同じ問題を抱えています。ある実行コード(CPUエミュレーション)と別の実行コード(APUエミュレーション)を切り替えるのは、動作中に歯車が止まるようなもので、L1命令キャッシュを使い切ってしまうなどの問題があります。 297 | 298 | しかし、協調スレッディングでスタックポインタを変更するのは、L1キャッシュミスどころではないほどのペナルティです。私の経験則ですが、単一レベルのステートマシンを使ってプロセッサをエミュレートすることができれば、協調スレッドによるアプローチよりもかなり高速に動作することが多いです。 299 | 300 | 一方で、前述のCPUのオペコードの例のように、2段階以上のステートマシンになると、協調スレッドモデルが性能面で明らかに勝ることになります。 301 | 302 | また、協調スレッドがステートマシーン方式より遅い場合でも、先ほど説明したjit同期の適用度合いによって結果が変わってきます。 303 | 304 | ## 設計の選択肢 305 | 306 | 高速な動作を望んでいるなら、エミュレート対象のシステムが、 307 | 308 | ``` 309 | ・コンポーネント間でどれくらいリソースを共有しているか 310 | ・ステートマシンにするなら深さはどれくらいか 311 | ``` 312 | 313 | などを考えて、協調型スレッドとステートマシンを使い分けるべきです。 314 | 315 | Higanエミュレータでは、一貫性を重視してすべてのプロセッサを協調スレッドでエミュレートすることにしました。もちろんパフォーマンス上のペナルティは承知の上です。 316 | 317 | ## シリアライズ 318 | 319 | おそらくエミュレータ開発者にとって、協調型スレッディングの最大の関心事はシリアライズ、つまりセーブステートの保存と読み込みです。 320 | 321 | セーブステートは、進捗状況を保存したり復元したりするのに便利なだけでなく、遅延のないネット対戦や入力の先取りなどの機能の実現に必要なものです。 322 | 323 | ステートマシンを使用する場合、シリアライズとは、先ほどのコード例で言えば、`opcode`、`cycle`、`subcycle`などの変数をすべて保存・復元することです。 324 | 325 | しかし、協調型スレッディングを使用している場合、これらの情報は暗黙の了解となり、コールフレームやCPUコンテキストレジスタの形で個々のスタックに保存されます。 326 | 327 | 協調型スレッディングでシリアライズを実装することは可能ですが、複雑なテーマなので、このテーマについては別の記事を書きました。その記事はこちらからご覧いただけます。 328 | 329 | [Cooperative Threading - Serialization](https://near.sh/articles/design/cooperative-serialization) 330 | 331 | ## 実装 332 | 333 | ここまで読んでいただいて、協調スレッディングを試してみようと考えている人は、実装する言語で協調型スレッドモデルの実装が必要です。 334 | 335 | C++20では、コルーチンの拡張機能が提案されていますが、この記事を書いている時点ではまだ実装が不完全なのでエミュレーションに役立つかどうかはわかりません。おそらく最大の障壁は、C++20のコルーチンがスタックレスであることで、中程度の複雑さのものであってもその可能性が大きく制限されてしまうことです。 336 | 337 | 代わりに、私はC89コードで独自の協調スレッドライブラリを開発しましたが、これはC++コードでも使用できます。そのソースコードは[`bsnes/libco`](https://github.com/bsnes-emu/bsnes/tree/master/libco)で見ることができます。 338 | 339 | 基本的には、コンテキストスイッチングを実装するために、サポートされている各アーキテクチャのアセンブラを触る必要があります。なぜなら、CPUのコンテキストレジスタやスタックフレームを直接変更することは、ほとんどのプログラミング言語では許可されていないからです。しかし、x86のコンテキストスイッチはわずか12行のアセンブラコードで、32bitARMはわずか3行で実装することができるため、思っているよりは簡単なはずです。 340 | 341 | `libco`はISCライセンスで、非常に多くのCPUアーキテクチャをサポートしているため、`libco`を使うなら協調スレッディングの実装をすべて自分で行う必要はありません。 342 | 343 | `libco`を使わなかったとしても、ほとんどのメジャーな言語で協調スレッディングの実装例やライブラリがあるので、それを使ってもいいですし、それでも不満なら全部自分で作ってもいいでしょう。 344 | 345 | ## 終わりに 346 | 347 | いずれにしても、協調スレッディングは、ステートマシンを管理する負担を取り除くことで、コードを読みやすくするという点で、私のエミュレータに独自の利点を与えてくれていると思いますし、クロックサイクルに正確に対応することも簡単です。 348 | 349 | 繰り返しになりますが、協調スレッディングはレトロゲームのエミュレータを書くための最良の方法とは限りません。 350 | 351 | 最後まで読んでくれてありがとうございました。 352 | 353 | -------------------------------------------------------------------------------- /near/design/hierarchy/README.md: -------------------------------------------------------------------------------- 1 | # 階層構造 2 | 3 | よくできたエミュレータ、特にマルチシステムエミュレータで最も重要な点の一つは、システム全体の状態を把握するためのしっかりとした階層設計です。 4 | 5 | つまり、システムの能力、変更可能な設定、接続可能な外部周辺機器(カートリッジ、フロッピーディスク、コントローラなど)などを表現します。 6 | 7 | やみくもにやってもエミュレータは動作しますが、大きなコードベースを後からよりよいデザインに変更するよりも、エミュレータの設計をあらかじめしっかり考えておく方が得策です。 8 | 9 | 優れた階層設計があれば、エミュレータのコアをGUIから見て抽象化することができますし、他にも多くの利点があります。例えば、複数のバックエンド(例えば異なるオペレーティングシステム用)を提供できるだけでなく、エミュレータ開発から撤退した後も、コアのコードはよりクリーンで抽象化されており、よりポータブルなものになります。 10 | 11 | より多くのシステムを簡単にエミュレートすることができ、それらは既存のユーザーインターフェイスにそのままプラグインすることができます。 12 | 13 | これらの詳細をハードコーディングする以外に、エミュレータ階層を構造化するには、リストとツリーという2つの主要な方法があります。 14 | 15 | ## 背景 16 | 17 | 階層化が必要な第一の理由は、システムの状態が静的ではないからです。 18 | 19 | スーパーファミコンには、カートリッジポート、拡張ポート、そして2つのコントローラポートがあると説明することができます。 20 | 21 | しかし、[スーファミターボ](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%BC%E3%83%95%E3%82%A1%E3%83%9F%E3%82%BF%E3%83%BC%E3%83%9C)のアダプターを見てみましょう。 22 | 23 | ![](sufami-turbo.jpeg) 24 | 25 | スーファミターボは、バンダイから発売された機器で、製造コストの低い小型のスーファミターボゲームを遊ぶことができます。 26 | 27 | また、友人が持っているゲームを隣に接続すると、2つのゲームが合体してさらに大きなゲームになるというユニークな仕掛けもありました。 28 | 29 | 例えば、『SDウルトラマン』を合体させると、それぞれのゲームに登場するファイターが加わって1つのゲームになります。『ぽいぽい忍者ワールド』を2本組み合わせれば、それぞれのプレイヤーが保存しているキャラクターデータを使って協力プレイが可能になります。 30 | 31 | このカートリッジをスーファミのカートリッジポートに接続すると、面白いことに、スーファミターボのカートリッジポートが2つ追加され、システムの階層が広がったことになります。 32 | 33 | 次はスーファミのスーパーマルチタップについてみていきましょう。 34 | 35 | ![](super-multitap.jpeg) 36 | 37 | このデバイスをコントローラポートに接続することで、システムの階層はさらに4つのコントローラポートで拡張され、あらゆる種類のコントローラを受け入れることができます。 38 | 39 | もちろん、特定のコントローラーにしか対応していないので、スーパーマルチタップにスーパーマルチタップを連結して16人制のゲームを作ることはできません。しかし、標準的なゲームパッド以外にも対応していました。 40 | 41 | ## ハードコーディング 42 | 43 | ほとんどのスタンドアロンの単一システムのエミュレータは、作業している特定のシステムのUI部分にすべてをハードコーディングするという素朴なアプローチをとっています。 44 | 45 | 例えば、スーファミのエミュレータなら、従来の『Load Cartridge』というメニューオプションに加えて、『Load Sufami Turbo Cartridge』というメニューを追加したいと思うかもしれません。 46 | 47 | また、スーパーゲームボーイに対応する場合は、カートリッジスロットをもう1つ追加します。サテラビューへの対応も同じようにします。他にも対応すべきものが増えた時は同じことをします。 48 | 49 | そしてスーパーマルチタップは、標準的なゲームパッドを4本接続したコントローラーのような扱いになります。 50 | 51 | しかし、これでは非常に拡張性がなく(ツインタップやその他のコントローラーをスーパーマルチタップに接続したい場合はどうすればいいのでしょうか)、抽象化もされていません。 52 | 53 | UI部分はあなたのスーパーファミコンコアを利用することに特化したUIになっていて、ゲームボーイアドバンスのコアを作ったとしてもUI部分は使えないでしょう。 54 | 55 | 先の例だと『Load Cartridge』というメニューはゲームボーイアドバンスでも使い回せますが、『Load Sufami Turbo Cartridge』というメニューはGBAでは使えません。 56 | 57 | しかし、もしあなたが絶対に1つのエミュレータコアしか書きたくないと確信しているのであれば、この方法で非常に親しみやすいUIを多くの作業をせずに作成できることは間違いありません。 58 | 59 | ## リスト 60 | 61 | 簡単な改善策は、システムの階層を一連のリストとして表現することです。カートリッジのポート、コントローラーのポート、設定などです。スーパーファミコンの場合、これは次のようになります。 62 | 63 | ``` 64 | - Cartridge Port 65 | - Expansion Port 66 | - Controller Port 1 67 | - Controller Port 2 68 | ``` 69 | 70 | これをAPIで公開すると、次のようになります。 71 | 72 | ```c++ 73 | struct Object { string name; ... }; 74 | struct Controller : Object { vector buttons; ... }; 75 | struct Setting : Object { vector availableValues; ... }; 76 | 77 | struct CartridgePort : Object { string fileExtension; ... }; 78 | struct ControllerPort : Object { ... }; 79 | 80 | struct Emulator : Object { 81 | vector cartridgePorts; 82 | vector controllerPorts; 83 | vector availableControllers; 84 | vector settings; 85 | }; 86 | struct SuperNintendo : Emulator { ... }; 87 | 88 | SuperNintendo::SuperNintendo() { 89 | cartridgePorts.append({...}); 90 | controllerPorts.append({"Controller Port 1", ...}); 91 | controllerPorts.append({"Controller Port 2", ...}); 92 | availableControllers.append({"Gamepad", ...}); 93 | availableControllers.append({"Super Multitap", ...}); 94 | settings.append({"CPU Revision", {"1", "2"}}); 95 | } 96 | ``` 97 | 98 | これで、オブジェクト`Emulator`の内部リストを繰り返し処理することで、渡されたエミュレータオブジェクトに基づいて自分自身を構築するユーザーインターフェースを構築できるようになりました。 99 | 100 | 例えば、露出している各カートリッジポートにゲームをロードするためのメニュー項目を作成したり(SNESには1つしかありませんが、MSXとNintendo DSには2つあります)、各コントローラポート(あれば)にメニューグループを作成して、`availableControllers`で定義した利用可能なコントローラリストから各コントローラタイプを1つずつ入力したりすることができます。 101 | 102 | スーファミターボのカートリッジがカートリッジポートにロードされると、エミュレーションコアはスーファミターボのカートリッジスロットごとにカートリッジポートリスト`cartridgePorts`に2つのアイテムを追加します。 103 | 104 | ユーザーインターフェースは、エミュレータオブジェクトを再スキャンして、スーファミターボの2つのカートリッジスロットにゲームをロードするための2つの新しいメニューオプションを表示するように再構成する必要があります。 105 | 106 | 同様に、ゲームをアンロードする際には、この2つのカートリッジポートは消え、ユーザーインターフェースはゲームロードメニューからそれらを削除します。 107 | 108 | スーパーマルチタップでも、コントローラーのポートを増やすことで同様の状況になります。 109 | 110 | ここで問題があります。スーファミターボのスロットが、現在接続されているベースのスーパーファミコンカートリッジの子孫であることも、追加のコントローラーポートのスロットがスーパースーパーマルチタップの子孫であることも、まったく明らかではないのです。 111 | 112 | また、上記では拡張ポートをAPIとして公開していなかったので、かなり面倒なことになっています。 113 | 114 | 拡張ポートとは何でしょうか? それはシステムによります。セガCDのようにCD-ROMドライブかもしれませんし、メガドライブのように3つ目のコントローラーになるかもしれません。MSXのように1つのポートがハードウェアの拡張とゲームの両方に使われることもあります。スーパーファミコンの『[エクサテインメント・バイク](https://snescentral.com/article.php?id=0793)』のように、エクササイズバイクにもなりえます。 115 | 116 | ![](exertainment-bike.jpeg) 117 | 118 | このように拡張ポートに繋がったデバイスを分類してやる必要があるのです。 119 | 120 | ## ツリー 121 | 122 | 完全な階層を表現するには、ツリーを使うのが正しい方法です。 123 | 124 | 必要なオブジェクトの種類をハードコードしたリストではなく、エミュレートするシステムを表すツリーのルートから始めて、カートリッジポート、拡張ポート、コントローラポート、調整可能な設定、ビデオ出力、オーディオ出力などをツリーのブランチとして表現します。 125 | 126 | これらの枝は、成長して自分の枝を含むようになったり、木の葉として終了したりします。スーパーファミコンの場合、この木は次のようになります。 127 | 128 | ``` 129 | - Super Nintendo 130 | - Cartridge Port => Sufami Turbo 131 | - Sufami Turbo Port - A 132 | - Sufami Turbo Port - B 133 | - Expansion Port 134 | - Controller Port 1 => Gamepad 135 | - Controller Port 2 => Super Multitap 136 | - Super Multitap Port - A => Twin Tap 137 | - Super Multitap Port - B => Twin Tap 138 | - Super Multitap Port - C 139 | - Super Multitap Port - D 140 | - CPU 141 | - Revision => 2 142 | ``` 143 | 144 | そのため上の例では、スーファミターボとスーパーマルチタップを接続すると、ツリーが拡大していく様子がわかります。 145 | 146 | ツリーの内部構造は以下のようになります。 147 | 148 | ```c++ 149 | struct Object { string name; vector> children; ... }; 150 | struct SuperNintendo : Object { ... }; 151 | 152 | struct Peripheral : Object { string type; ... }; 153 | struct Cartridge : Peripheral { ... }; 154 | struct Expansion : Peripheral { ... }; 155 | struct Controller : Peripheral { ... }; 156 | ``` 157 | 158 | 新しくエミュレートされたシステムに新しいタイプのオブジェクトが必要になった場合、それを追加することができます。 159 | 160 | ユーザーインターフェースも新しいタイプのオブジェクトに対応するように拡張する必要がありますが、既存のコードはすべて継続して動作します。 161 | 162 | ## 抽象化 163 | 164 | もうお分かりかもしれませんが、ツリーを構成するためには、すべてのノードが同じ型でなければなりません。つまり、すべてが`Object`を継承しているのです。リストの例では、利便性のためだけに継承しましたが、ツリーでは必要です。 165 | 166 | 必要ではありませんが、便利なのは、`shared_ptr`を使って、子オブジェクトを参照カウントするようにしたことです。 167 | 168 | これにより、ユーザーインターフェースは、ダングリングポインタを恐れることなく、ツリー要素のコピーを保持することができます。慎重に使わないと危険ですが、常にツリー全体を反復する必要がなくなるので、ユーザーインターフェイスのデザインがシンプルになります。これはあなた次第です。 169 | 170 | もちろん、次のようにオブジェクトを抽象化して、リストベースのデザインを実装することもできます。 171 | 172 | ```c++ 173 | struct Object { string name; ... }; 174 | struct Emulator : Object { vector objects; ... }; 175 | struct SuperNintendo : Emulator { ... }; 176 | ``` 177 | 178 | しかし、現時点では、子孫を表現するためにツリー型を使用しない理由はありません。カートリッジポート、コントローラポート、コントローラなどの種類ごとに複数のリストを用意する理由は、すべてをハードコーディングすることと、100%完全に抽象化されたデザインを必要とすることの中間に位置するからです。 179 | 180 | しかし、私が説明したような中間的なリスト階層の問題点は、いくつかのシステムでは機能しますが、より多くのシステムをエミュレートするようになると、自重で崩れ始めることです。少なくとも、1つのシステムでしか機能しないようなハードコードされたデザインよりは回復力があります。しかし、最終的には強固な基盤とは言えないと感じています。 181 | 182 | ## 歴史 183 | 184 | 注:この部分は、一般的なアドバイスから歴史の授業へと移行する部分です。警告しておきますね。 185 | 186 | 上記の3つの階層構造は、過去15年間にエミュレータを書いてきた私自身の個人的な経験から生まれたものです。 187 | 188 | 私は2004年に、ハードコーディングによりスーファミターボとBS-Xサテラビューのサポートをしてbsnesを公開しました。 189 | 190 | 長い年月を経て、私はUIの設計に飽き飽きしていました。 191 | 192 | bsnesはいくつも設計上の問題点を抱えており、人気のあるツールキットを使ってインターフェースを構築していましたが、そのツールキットは非常に大規模で、(少なくとも2010年の時点では)恐ろしくバグが多いことが判明し、問題の解決にはなりませんでした。 193 | 194 | 私は当初、ユーザーインターフェースの開発を他の開発者に委ねたいと考えていたため、エミュレーションコアとユーザーインターフェースを分離するために、bsnes用のAPIレイヤーを構築する必要がありました。 195 | 196 | そして、これを使って、libsnesという上記のようなリストベースの設計のAPIを持ったコアを作りました。 197 | 198 | Themaister氏は、libsnesを引き継いで、libsnesをコア部分とした独自のUIを作り、それをSSNESと呼びました。 199 | 200 | しかし、スーファミ用に設計されたAPIでは限界があります。このことに気づいたのは、ゲームボーイのエミュレータを前提としたスーパーゲームボーイのエミュレーションに取り組み始めた頃でした。 201 | 202 | Themaister氏はSnes9Xをlibsnesに移植することから始め、その後、私とシステムは違うが、SNES以外の他のエミュレータを同じAPIに移植するという、非常に運命的なことをしました。 203 | 204 | このあたりから方法論が分かれてきました。Themaister氏は、より多くのシステムをサポートするためにCベースのAPIとしてlibsnesを拡張し、このAPIは最終的にlibretroに改名され、SSNESを動かし、Retroarchと改名されました。 205 | 206 | 一方、私の方では、libsnesを完全に破棄して、C++によるオブジェクト指向のデザインに移行し、`Emulator::Interface`と名付けました。そして、bsnesはエミュレートされたシステムを増やしながら拡張を続け、Higanとなりました。 207 | 208 | ![](higan_v105.png) 209 | 210 | libretroと`Emulator::Interface`の間には基本的な違いはなく、一時的には両者の間に翻訳層が存在していたくらいです。 211 | 212 | 分裂の理由は、私が自分のエミュレータを維持したいと思ったことと、Themaister氏が複数のプロジェクトのエミュレータを取り込みたいと思ったことです。 213 | 214 | `Emulator::Interface`の下でhiganはどんどん広がっていきましたが、MSXのエミュレーションを試みるという致命的なミスを犯してしまいました。 215 | 216 | ![](msx.jpeg) 217 | 218 | 一見シンプルな80年代のZ80ベースのコンピューターが、higanの変化の先駆けとなったのです。 219 | 220 | MSXは、日本では多くのメーカーが汎用的なパソコンを作っていたときに生まれたものの一つでした。 221 | 222 | そして、それぞれのメーカーは、このシステムを独自のセールスポイントとして、幻想的とも言えるような無数の方法で拡張していきました。 223 | 224 | 私のリストベースのデザインでは、複数のゲームスロットを持つシステムは考えられませんでした。しかし、このMSXは、カートリッジポート、フロッピーディスクドライブ、クイックディスクドライブ、さらにはカセットテープリーダーをいくつでも搭載することができるようになっていました。 225 | 226 | 上の写真は、側面に2つのカートリッジポートとフロッピーディスクドライブを備えたシステムです。背面の拡張ポートからは、カートリッジを3本追加できるアダプターが接続されていました。 227 | 228 | 一方、他のコアにも限界が見えてきました。スーファミのエミュレーションコアでは、スーファミターボ、BS-Xサテラビュー、スーパーゲームボーイに対応するために、ベースカートリッジをセットして、エミュレーションコアがユーザーインターフェースにスロット付きのカートリッジ(スーファミターボの場合は2つ)をセットするように要求するという粗いデイジーチェーンハックを思いつきました。 229 | 230 | カートリッジポートを追加するシステムは考えていなかったので、スーパーマルチタップはゲームパッドを4つ並べるようにハードコーディングしてしまい、ツインタップのエミュレーションができませんでした。 231 | 232 | しかし、メガドライブの「マイクロマシン2」が登場して、私の考えは覆されました。このゲームは、スーパーマルチタップのアダプターを売ろうとするのではなく、カートリッジ自体にポートを追加したのです。 233 | 234 | ![](micro-machines-2.jpeg) 235 | 236 | higan v106までの間、私はリストベースのデザインの限界を回避しようとしていましたが、最終的には、これらの様々なシステムや周辺機器を扱うことができる新しい設計が必要であることが明らかになりました。 237 | 238 | 問題は、libsnesも`Emulator::Interface`も、私がこの記事で説明したリストベースの設計の真の姿ではなかったということです。 239 | 240 | メガドライブには、1つのカートリッジポート、1つの拡張スロット、1つの拡張コントロール端子、そして2つのコントローラポートがありました。 241 | 242 | ユーザーインターフェースは、ゲームカートリッジがコントローラーポートを増やすことに対応できませんでした。 243 | 244 | マイクロマシン2のようなケースに対応できる可能性があるのは、上記のリストベースのシステムだと説明しましたが、ありとあらゆるケースに対応するにはどうしたらいいのか、何ヶ月もかけて考えた結果、私が出した答えはツリーベースのデザインであり、それをhigan v107以降に実装しました。ダイナミックに表現するのであれば、リストではなくツリーで表現した方が、コントローラポート3と4がカートリッジから出ていることが明確になると考えたのです。 245 | 246 | higan v106のように、コントローラのポートごとにメニューを表示していたのでは、この階層性を伝えることができませんので、このツリービューを中心にデザインされた新しいユーザーインターフェースが必要でした。 247 | 248 | いくつかの新しいエミュレーションコアの追加と、特に新しいツリーベースのデザインのために、higan v106からv107まで2年以上かかりました。その結果がこれです。 249 | 250 | ![](higan_v107.png) 251 | 252 | そのため、どんな設定でも表現できるようになりましたが、その反面、ユーザーインターフェースが非常に不自然で、慣れるのに時間がかかるようになってしまいました。 253 | 254 | 計画の初期段階でそうなることは分かっていたので、伝統的なエミュレータのユーザーインターフェースでデザインされたスタンドアロンのSNESエミュレータとしてbsnesを復活させることにしました。 255 | 256 | ![](bsnes_v112.png) 257 | 258 | 現在のbsnesは、まだ`Emulator::Interface`をベースにしています。これはリストベースのデザインですが、スロット付きカートリッジやスーパーマルチタップに関しては、前述のようにハードコードされた要素があります。 259 | 260 | ## 終わりに 261 | 262 | 最終的にどのような設計に仕上げるかはあなた次第です。念の為言っておきますが、この記事では、あるアプローチが他よりも優れているという主張をしているわけではありません。 263 | 264 | Snes9Xはハードコーディングされていても例外的にうまくいっていますし、Retroarchは疑似リストベースのアプローチで非常に人気があります。higanは個人的には好きなデザインですが、使いやすさやユーザーの親しみやすさという点では限界があると認識しています。そのため、私もbsnesでは擬似リストベースの手法を使っています。 265 | 266 | この記事では、私が認識している設計パラダイムと、個人的に試した設計パラダイムを紹介しましたので、あなたが何を達成したいかに応じて、ご自身で判断していただければと思います。 267 | 268 | ただし、よく考えてから始めてください。成熟したエミュレータ、特にhiganのようなマルチシステムのエミュレータでは、階層的なパラダイムを行き来するのは大変な作業です。 269 | -------------------------------------------------------------------------------- /near/design/hierarchy/bsnes_v112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/bsnes_v112.png -------------------------------------------------------------------------------- /near/design/hierarchy/exertainment-bike.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/exertainment-bike.jpeg -------------------------------------------------------------------------------- /near/design/hierarchy/higan_v105.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/higan_v105.png -------------------------------------------------------------------------------- /near/design/hierarchy/higan_v107.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/higan_v107.png -------------------------------------------------------------------------------- /near/design/hierarchy/micro-machines-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/micro-machines-2.jpeg -------------------------------------------------------------------------------- /near/design/hierarchy/msx.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/msx.jpeg -------------------------------------------------------------------------------- /near/design/hierarchy/sufami-turbo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/sufami-turbo.jpeg -------------------------------------------------------------------------------- /near/design/hierarchy/super-multitap.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/design/hierarchy/super-multitap.jpeg -------------------------------------------------------------------------------- /near/design/schedulers.md: -------------------------------------------------------------------------------- 1 | # スケジューラ 2 | 3 | エミュレータの基本部分を固める上で最も重要な基礎の一つは、質の高いスケジューラです。 4 | 5 | ターゲットシステムの複数のプロセッサ(CPU,GPU,APU,...etc)をエミュレートする場合、各プロセッサが他のプロセッサに対して時間的にどの位置にあるかを追跡し、適切なクロックスピードでエミュレートできるようにする方法が必要です。 6 | 7 | これには大きく分けて2つの方法があり、どちらを選択するかは、エミュレートしようとしているシステムの複雑さによります。 8 | 9 | 前置きが長くなりましたが、この記事では、エミュレートされた各プロセッサをスレッド、特に協調スレッディングやステートマシンをベースにしたスレッドと呼んでいます。 10 | 11 | したがって、スレッドスケジューラの実装方法については後述します。これらのスレッドは、一般的なプロセッサ、グラフィックチップ、オーディオチップなど、システム内のあらゆるコンポーネントを指します。 12 | 13 | ## 相対スケジューラ 14 | 15 | 相対スケジューラは、速くて簡単で、うまくやれば基本的に時間の管理が完璧にできます。 16 | 17 | 最大の弱点は、2つのスレッド間で1対1の関係でしか動作しないことです。もちろん、1対1の関係をいくらでも作ることはできますが、最終的には複雑すぎてコストがかかるため、あまりいい結果にはなりません。 18 | 19 | 相対スケジューラが有効な使用例はスーパーファミコンであり、逆にひどい使用例はセガCDでしょう。 20 | 21 | 相対スケジューラを実装するには、1対1のスレッド関係ごとに符号付き64bit整数を保持する必要があります。 22 | 23 | スーパーファミコンのレイアウトを見てみると、基本システムは、汎用プロセッサ(CPU)、オーディオコプロセッサ(SMP)、グラフィックチップセット(PPU)、オーディオジェネレータ(DSP)で構成されています。 24 | 25 | スーファミの場合、DSPはSMPとしか直接通信できず、PPUはCPUとしか直接通信できません。そのため、以下の組み合わせに対して64bit整数が必要になります。 26 | 27 | ``` 28 | CPU <-> PPU 29 | CPU <-> SMP 30 | SMP <-> DSP 31 | ``` 32 | 33 | **CPU <-> SMP** 34 | 35 | 例えば、21,477,272hzで動作するCPUと、24,576,000hzで動作するSMPとの相対的な時間を追跡したいとします。この目的のためにint64型の`cpu_smp`というスケジューラ変数を用意します。 36 | 37 | これは、相対的なタイミングのクロックを表します。電源投入時とリセット時には、このクロック変数をゼロに設定します。 38 | 39 | `cpu_smp >= 0`のときはCPUがSMPよりも実行が進んでいます。逆に`cpu_smp < 0`のときはSMPのほうがCPUより実行が進んでいます。 40 | 41 | CPUをNクロック実行した場合は`cpu_smp`から`N * 24,576,000`を引き、逆にSMPをNクロック実行した場合は`cpu_smp`に`N * 21,477,272`を加えます。 42 | 43 | `cpu_smp`が0のときはお互いのプロセッサが、まさに同じ時間状態(同期)であることを示しています。 44 | 45 | ここで重要なのは、CPUの実行は`N * SMP_frequency`を引き、SMPの実行は`N * CPU_frequency`を加える点です。 46 | 47 | 相手側の周波数の倍数だけステップすることで、相対的な時間を保っています。 48 | 49 | **CPU <-> PPU** 50 | 51 | CPUとPPUは両方ともクロックが 21,477,272Hz です。なのでSMPの場合と違って、相手の周波数をステップ掛けなくても同期をとることが可能です。 52 | 53 | **問題点** 54 | 55 | 問題点として、スケジューラ変数のオーバーフロー/アンダーフローが挙げられます。特にCPU <-> SMPの場合はクロック数をかけている分、オーバーフロー/アンダーフロー問題がPPUよりも起こりやすいです。 56 | 57 | This gives us 63-bits of usable precision in either direction, and 2^63 / 24,576,000 tells us that the CPU can advance up to 375,299,968,947 clocks ahead of the SMP before cpu_smp would underflow. That's 17,474 seconds, which seems like more than enough time to ever worry about it. But the general idea is that you would never want to allow the CPU to run more than that amount of time ahead of the SMP. 58 | 59 | You can just slightly start to see the issue with relative scheduling by noting that whenever the CPU steps by N clocks, it needs to update both cpu_smp and cpu_ppu. The more processors you add in, the more work this becomes. 60 | 61 | Take the Sega CD, where you have two 68K CPUs, a Z80 APU, a VDP graphics chip, a PSG audio chip, a YM2612 FM synthesis chip, a CD drive controller, a custom ASIC graphics scaler, and more ... almost all of which can directly communicate with each other, and a relative scheduler turns out to be a rather bad choice. 62 | 63 | ## 絶対スケジューラ 64 | 65 | 絶対スケジューラは、やや複雑ですが、任意の1つのスレッドの現在時刻を他の任意のスレッドに対して、つまりN:Nで検査するために使用することができます。このため、前述のセガCDなどのシステムには最適です。 66 | 67 | ここでの考え方は、各スレッドがそれぞれ64bitの符号なし整数のタイムスタンプを保持しており、スレッドを実行する際に、そのカウンタを実行時間分インクリメントするというものです。このカウンタが他のスレッドのカウンタよりも進んでいれば、そのスレッドは時間が進んでいることになります。 68 | 69 | もちろん、これらのカウンタもいずれはオーバーフローしますので、定期的に、すべてのスレッドのカウンタをチェックして最小の値(つまり、時間的に最も後ろにあるスレッド)を見つけ、その値をすべてのスレッドから差し引く必要があります。さらに、どのスレッドも64bitのカウンタをオーバーフローさせるほど長く実行できないようにしなければなりません。 70 | 71 | 絶対スケジューラーで問題となるのは、スーパーファミコンのように 21,477,272Hz と 24,576,000Hz のように異なる複数のクロック周波数が、ナノ秒のような一貫した時間単位に直接対応しないことです。 72 | 73 | そのため、まずすべてのプロセッサのクロックレートをナノ秒などの時間単位に正規化する必要があります。実際の発振器も100%正確ではないので、絶対的な完全同期は必要なく、多少の端数丸め誤差は許容できます。しかし、それでもナノ秒よりははるかに良い結果が得られます。 74 | 75 | また、前述のように、パフォーマンス上の理由から、浮動小数点ではなく、64bitの符号なし整数を使用します。 76 | 77 | まず最初に定義しなければならないのは、64bitの範囲で表現できる時間の長さで、これは未来の最も遠いスレッドが過去の最も遠いスレッドよりも進んでいる最大の時間になります。 78 | 79 | 一般的には、1秒という時間を選択するのが良いと思います。スレッドを正規化する必要があることを検出するために、私は64bitを使用しています。つまり、カウンタが2^63に達したときは、時間的に最も遅れているスレッドを見つけて、そのカウンタをすべてのスレッドから差し引くときであることを示しています。 80 | 81 | そこで、1秒を表す定数`Second`を`2^63 - 1`と定義します。 82 | 83 | 1秒に収まる数字の数がわかったので、各スレッドの頻度をそれに合わせて正規化することができます。 84 | 85 | そこで、各スレッドの定数スカラーを`Second / Frequency`と定義します。 86 | 87 | スレッドがNクロック進むたびに,その64bit符号なしカウンタは`N * (Second / Frequency)`だけインクリメントされます。 88 | 89 | 1秒あたり`2^63 - 1`個のティックがあるとすると、attoseconds(10^18)の10倍の精度で時間を追跡できることになります。 90 | 91 | 64bitのCPUでは128bitの演算が可能であり、uint128などの型を使えば`2^127`の精度が得られます。もちろん、実際にはその必要はありません。 92 | 93 | 繰り返しになりますが、このバランスをどうとるかは自由です。精度を上げることで最大先読み時間(1秒以上)を長くしてもいいですし、先読み時間を長くして精度を上げてもいいでしょう。 94 | 95 | しかし、実際には、上記の64bit整数と1秒の時間間隔は、ほぼすべてのユースケースで十分なはずです。 96 | 97 | ## 実装例 98 | 99 | [bsnes](https://github.com/bsnes-emu/bsnes/blob/master/bsnes/sfc/sfc.hpp)では相対スケジューラを、[higan](https://github.com/higan-emu/higan/tree/master/higan/emulator/scheduler)では絶対スケジューラを採用しています。 100 | 101 | ご覧のように、後者はかなり複雑で、スーパーファミコンという単純なケースでは、相対スケジューラを使った方がパフォーマンスが高いのです。 102 | 103 | しかし、higanは非常に多くのシステムをエミュレートしており、その多くは相対スケジューラではうまく機能しないため、代わりに絶対スケジューラを使用しています。 104 | 105 | -------------------------------------------------------------------------------- /near/input/latency.md: -------------------------------------------------------------------------------- 1 | # 入力遅延 2 | 3 | ゲーム機のエミュレーションには、さまざまな遅延要因があります。 4 | 5 | この記事では、エミュレータ開発者が直接コントロールできる入力遅延の原因を説明し、それを最小化するための私の方法を提案します。 6 | 7 | ## ポーリング 8 | 9 | エミュレートされたゲームシステムは、通常、エミュレートされたフレームごとに1回、VBlank割り込みの間にコントローラからの読み込みを行います。 10 | 11 | 技術的には、ゲームはいつ何度でもコントローラをポーリングすることができますが、99.9%のゲームは上記の戦略に従います。 12 | 13 | 通常、入力はメモリにマッピングされたI/Oレジスタからポーリングされて得られます。 14 | 15 | その後、エミュレータは、エミュレートされたマッピングを物理的なキーボード、マウス、またはゲームパッドに変換し、前記I/Oレジスタの読み取りから現在のボタンの状態を返す必要があります。 16 | 17 | ## 典型的な処理フロー 18 | 19 | ほとんどのエミュレータは、フレームごとに実際のハードウェアデバイスをポーリングするように設計されており、次のようなランループを実行しています。 20 | 21 | ```c++ 22 | void Program::run() { 23 | while(stopped() == false) { 24 | hardware.pollInputs(); // 1 25 | emulator.runFrame(); // 2 26 | video.drawFrame(); // 3 27 | } 28 | } 29 | ``` 30 | 31 | **1. `hardware.pollInputs()`** 32 | 33 | 上記のコードで、`hardware.pollInputs()` は、DirectInput、XInput2、SDLなど、利用可能なハードウェア入力APIに問い合わせを行い、その結果をキャッシュして後で使用します。 34 | 35 | ```c++ 36 | void Hardware::pollInputs() { 37 | keyStates = directInput.pollKeyboard(); 38 | } 39 | ``` 40 | 41 | **2. `emulator.runFrame()`** 42 | 43 | 次に `emulator.runFrame()` では、1フレーム分のビデオデータが揃うまでエミュレータを継続して動作させます。 44 | 45 | このときにエミュレーションコアの内部では、ゲームパッドの状態をメモリマップしたI/Oレジスタが読み込まれると、`hardware.pollInputs()`によってキャッシュされていた状態が返されます。 46 | 47 | ```c++ 48 | uint8_t Emulator::pollGamepad() { 49 | uint8_t data = 0; 50 | data |= program.readInput(GAMEPAD_UP ) << 0; 51 | data |= program.readInput(GAMEPAD_DOWN) << 1; 52 | ... 53 | return data; 54 | } 55 | ``` 56 | 57 | これは、プログラムがエミュレートされた入力を、マッピングされたハードウェアの入力に変換することで行われます。 58 | 59 | ```c++ 60 | bool Program::readInput(uint inputID) { 61 | if(inputID == GAMEPAD_UP ) return hardware.keyStates[KEY_UP]; 62 | if(inputID == GAMEPAD_DOWN) return hardware.keyStates[KEY_DOWN]; 63 | ... 64 | } 65 | ``` 66 | 67 | **3. `video.drawFrame()`** 68 | 69 | 最後に、`video.drawFrame()`は、エミュレートされたビデオフレームを受け取り、Direct3D、OpenGL、SDLなどを使って、プログラムのオンスクリーンウィンドウに出力します。 70 | 71 | ```c++ 72 | void Video::drawFrame() { 73 | direct3D.draw(program.window, emulator.frame, emulator.width, emulator.height); 74 | } 75 | ``` 76 | 77 | 以上が典型的なエミュレータの処理フローです。 78 | 79 | マルチエミュレータのフロントエンドであるRetroArchはこのように書かれています。 80 | 81 | ## Missed Frames 82 | 83 | この記事では、1フレームあたりのスキャンラインが262本のスーパーファミコンを想定しています。 84 | 85 | ここでは、変数`V`はエミュレータが現在生成している垂直方向のスキャンラインを意味し、`V=0..261` と宣言しましょう。 86 | 87 | `emulator.runFrame()`がVBlank期間を含む1フレーム全体をエミュレートした場合、`hardware.pollInputs()`が呼ばれたときにはV=0になっています。 88 | 89 | この状態は、ゲームに読み込まれる`V=225`まで続くので、ポーリングされた結果を実際に使用するときには、ほぼ1フレーム分、16msが経過していることになります。 90 | 91 | 脱線しますが、エミュレータの性能が非常に高く、ビデオに同期している場合は、より早くこの状態に到達し、ホストマシンが独自のVBlank期間に達するまでプログラムをスリープさせることができます。エミュレータをオーディオに同期させたり、単に負荷の高いエミュレータであれば、この利点は否定されます。 92 | 93 | ## Avoiding Missed Frames 94 | 95 | ゲームは通常、画面を描画した後、VBlank期間に入ります。ゲームは通常、VBlank中にエミュレートされたコントローラをポーリングします。 96 | 97 | ここでもスーパーファミコンを想定して、NTSCモードでは`V=1..224`、PAL(オーバースキャン)モードでは`V=1..239`の間で画面をレンダリングしています。 98 | 99 | 1つの方法は、画面がレンダリングされた直後、入力がポーリングされる前にエミュレータの実行、つまり`emulator.runFrame()`を終了させることです。言い換えれば、エミュレートされたVBlank期間の開始時に終了することです。 100 | 101 | `emulator.runFrame()`がV=225(NTSCの場合。PALの場合はV=240)でリターンした場合、エミュレートされたマシンのVBlankハンドラに戻る直前に、ホストマシンの入力をポーリングします。 102 | 103 | RetroArchの入力遅延修正パッチはこのようにして作られており、この潜在的な1フレーム分のラグのペナルティを取り除くことを目的としています。 104 | 105 | ## Complications 106 | 107 | PALモードとは、SNESに15本のスキャンラインを追加してレンダリングするように指示する設定で、PALのディスプレイであれば容易に確認することができます。しかし、NTSCのゲームでもオーバースキャンを使うものと、PALのゲームでもオーバースキャンを使わないものがあります。 108 | 109 | 実際、病的ではありますが、VBlank期間中にオーバースキャンの設定を切り替えることも可能です。Titanによるメガドライブのデモシーンソフト『Overdrive 2』は、メガドライブのグラフィックチップを悪用して、オリジナルのハードウェアの能力を超えた追加のスキャンラインを引き出すことができます。 110 | 111 | しかし、スーファミの場合でも、オーバースキャンを無効にして`V=225`に達したとしても、それはフレーム全体がレンダリングされたことを保証するものではなく、ゲームがオーバースキャンをオンにして、喜んでスキャンラインを描き始めるかもしれません。これは、`V=225`で`emulator.runFrame()`を終了しようとする場合には大きな問題となります。 112 | 113 | また、ゲームが必ずしも正確に`V=225`でコントローラをポーリングするとは限らないことも考慮してください。ゲームによっては、画面が終了する前の`V=220`や、VBlankルーチンの終了直後の`V=261`で入力をポーリングするものもあります。 114 | 115 | 病的なゲームでは、利用可能なサイクルがあればいつでも入力をポーリングすることを選ぶかもしれず、結果としてポーリング位置は1フレームごとに変わるかもしれません。 116 | 117 | 上記の最適化はあまりにも粗いです。もっとうまくできるはずです。 118 | 119 | ## Polling Every Scanline 120 | 121 | PCエンジンのエミュレータである『Ootake』は、エミュレートされたスキャンラインごとに本物のハードウェア入力をポーリングすることで、この問題を改善しようとしています。 122 | 123 | これは確かに有効ですが、エミュレートされたフレームごとに262回の`DirectInput`によるポーリングイベントが発生します。60Hzのリフレッシュレートでは、1秒間に15,720回ものDirectInput APIの呼び出しが発生することになります。 124 | 125 | どんなに速いUSBデバイスでも1秒間に1,000回しかポーリングしないので、これは非常に無駄なことです。(しかも、OSのデフォルト設定では、USBデバイスのポーリング頻度は通常100回程度です。) 126 | 127 | ## Hardware Polling Overhead 128 | 129 | `hardware.pollInputs()`の呼び出しには、カーネルモードへのスイッチを必要とするハードウェアAPIへの問い合わせ、キーボード全体の状態の取得、それらの状態のエミュレートされた入力へのマッピングなどの作業が必要です。 130 | 131 | これが、エミュレータがこの処理をフレームごとに1回しか行わないようにしている理由です。このオーバーヘッドがなければ、`Program::readInput()`が`hardware.pollInputs()`自体を呼び出すだけの簡単な解決策になります。 132 | 133 | しかし、これには簡単な解決策があります。 134 | 135 | ## JITポーリング 136 | 137 | 今回紹介する解決案を私は、JITポーリング(Just-in-Time Polling)と呼んでいます。 138 | 139 | ホストのハードウェア入力が最後にポーリングされた時のタイムスタンプを保持することで、頻繁に呼び出されても実際のポーリングを行わないようにすることができます。 140 | 141 | この新しいデザインは次のようなものです。 142 | 143 | ```c++ 144 | void Hardware::pollInputs() { 145 | static uint64_t lastPollTime = 0; 146 | uint64_t thisPollTime = getHostMachineCurrentTimestampInMilliseconds(); 147 | if(thisPollTime - lastPollTime >= 5) { //latency timeout: 5 milliseconds 148 | keyStates = directInput.pollKeyboard(); 149 | lastPollTime = thisPollTime; 150 | } 151 | } 152 | ``` 153 | 154 | ```c++ 155 | void Program::run() { 156 | while(stopped() == false) { 157 | //we no longer have to call hardware.pollInputs() here 158 | emulator.runFrame(); 159 | video.drawFrame(); 160 | } 161 | } 162 | ``` 163 | 164 | ```c++ 165 | bool Program::readInput(uint inputID) { 166 | //we call hardware.pollInputs() here instead 167 | hardware.pollInputs(); 168 | if(inputID == GAMEPAD_UP ) return hardware.keyStates[KEY_UP]; 169 | if(inputID == GAMEPAD_DOWN) return hardware.keyStates[KEY_DOWN]; 170 | ... 171 | } 172 | ``` 173 | 174 | 上記のコードでは、`emulator.runFrame()`がいつ戻るかを気にする必要はありません。 175 | 176 | エミュレートされたシステムが入力をポーリングしようとすると、そのタイミングでホストマシンの入力をポーリングします。 177 | 178 | 5ミリ秒のタイムアウトがあるので、`Emulator::pollGamepad()`が`GAMEPAD_DOWN`の状態を取得するために`Program::readInput()`を2回目に呼び出しても、`hardware.pollInputs()`を2回目に呼び出すことはないということです。 179 | 180 | ハードウェアは入力が読み込まれる直前にポーリングされ、その後、エミュレーションコアによってすべての入力が1つのクラスタにまとめて読み込まれることがほとんどであるため、`hardware.pollInputs()`はフレームごとに1回しか起動されません。 181 | 182 | ハードウェアをポーリングした後、エミュレータは次の1フレームになったら実行を再度続けます。 183 | 184 | 1フレームは16ミリ秒なので、ポーリングのクールダウン期間の5ミリ秒より十分に大きいです。そのため、エミュレートされたゲームがいつ入力をポーリングしようと、ホストハードウェアは直ちにポーリングされます。 185 | 186 | また、スキャンラインごとに入力をポーリングし続けるような病的なゲームに遭遇した場合でも、最大入力遅延は5ミリ秒であることを保証しています。 187 | 188 | 99.9%のケースで、JITポーリングはDirectInputを1秒間に60回しかポーリングしませんが、これはこの記事で最初に取り上げたフレームの最初にポーリングを行う手法と同じ頻度です。 189 | 190 | 病的なケースでは、5ミリ秒のクールタイムが最大オーバーヘッドの上限となり、最大入力遅延も5ミリ秒、つまり1秒間に200回のDirectInput呼び出しが発生することになります。 191 | 192 | これが bsnes と higan の入力ポーリングのエミュレート方法です。 193 | 194 | RetroArchの遅延修正パッチは、RetroArchの設計では必要でしたが、bsnesのデザインには必要ありませんでした。さらに言えば、bsnesでRetroArchの遅延修正パッチを使うようにすると、SNESのPALモードをエミュレートして、VBlank期間中に切り替えられるようにすることにも支障をきたしたでしょう。 195 | 196 | ## クールタイムの変更 197 | 198 | JITポーリングの副次的効果として、現在5ミリ秒のポーリングクールタイムをユーザーが設定で変更可能にすることができます。 199 | 200 | 1ミリ秒という低い値を設定すれば、市場に出回っている1000hzのUSBポーリングゲームパッドやドライバーに追いつくことができます。 201 | 202 | また、1フレームのレンダリングにかかる時間よりも高い値を設定すれば、ラグシミュレーターになります。まともな人がなぜこのようなことをしたいのか全くわかりませんが、ユーザーにとって入力遅延は通常、かなり抽象的な概念です。240fps以上の高速カメラや、ビデオをずっと注意深く分析しない限り、直接測定することはできません。 203 | 204 | JITポーリングに大きめのクールタイムを設定することで、ユーザーは入力遅延が大きい時の動作をシミュレートすることができます。50msに設定すると、約2フレーム分の入力遅延が発生することになり、2フレーム分の遅延がゲームのプレイ体験や操作感にどのような影響を与えるかを、実際に体験することができます。 205 | 206 | ゲームの難易度を大幅に上げるには、入力遅延を増やすのが簡単です。格闘ゲームでは、片方のプレイヤーがもう片方のプレイヤーよりも遅延時間を長くすることで、簡単にハンディキャップを与えることができます。実用的か、役に立つかと言われると疑問ですが... 207 | 208 | ## 終わりに 209 | 210 | この手法をRetroArchなどの他のエミュレータに適用すれば、上流のエミュレータにパッチを当てることなく、すべてのエミュレーションコアの遅延を一気に解消できると考えています。 211 | 212 | さらに、すでに入力遅延の問題を解決しているエミュレータであっても、入力遅延をさらにわずかに短縮できる可能性があると考えています(例えば、私が考えている、例外としてありうる仮想的なゲームでは、VBlankの開始時ではなく終了時に入力をポーリングすることがあります)。 213 | 214 | 私はこの技術を発明したと主張しているわけではありません。世の中には何千ものエミュレーターがあります。この記事を書いている時点では、他の場所でこのようなことが行われていることを知りませんし、この技術がもっと普及すれば、エミュレータの助けになると思うと述べているだけです。 215 | 216 | また、これが特別に優れたアイデアだと主張しているわけではありません。ただ私にとってはうまくいった、むしろシンプルなアイデアというだけです。 217 | 218 | 私はこのアイデアの評価を求めていません。パブリックドメインだと思ってください。 219 | 220 | そして何よりも、他の人のアプローチを批判しているわけではありません。 221 | 222 | 私自身のエミュレータも、長年にわたって最初のアプローチを使用していました。 223 | 224 | 最近、エミュレータ開発のコミュニティでは、入力遅延を軽減することが非常に重要視されており、私はこのことを嬉しく思っています。 225 | 226 | RetroArchに実装されているrun-aheadと呼ばれるタイムシフトによるレイテンシー低減の記事など、このトピックについては今後も紹介していきたいと思っています。その時はお楽しみに。 227 | 228 | エミュレーション開発は競争ではありませんし、アイデアを出し合って改善していくことは誰にでもメリットがあります。それがこの記事で私がやりたいことです。 229 | 230 | お読みいただきありがとうございました。 231 | 232 | -------------------------------------------------------------------------------- /near/input/run-ahead/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/input/run-ahead/1.png -------------------------------------------------------------------------------- /near/input/run-ahead/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/input/run-ahead/2.png -------------------------------------------------------------------------------- /near/input/run-ahead/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/input/run-ahead/3.png -------------------------------------------------------------------------------- /near/input/run-ahead/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/input/run-ahead/4.png -------------------------------------------------------------------------------- /near/input/run-ahead/README.md: -------------------------------------------------------------------------------- 1 | # 先読み 2 | 3 | 先読み(run-ahead)とは、エミュレートされたビデオゲームの内部処理の遅延を除去するために使用される非常に興味深い技術の名称で、その結果、入力遅延はフレーム全体で減少します。 4 | 5 | PCの構成を最適化すれば、PC上で動作するソフトウェア・エミュレータで、ブラウン管TVを使用した実ハードウェアよりも低い遅延を実現することが可能になるのです。 6 | 7 | ## 概要 8 | 9 | ロックマンXをプレイしているときに、ジャンプボタンを押すと、主人公のXが空中に飛び出すとします。遅延のない理想的な状況では、ジャンプボタンを押した瞬間に、Xがジャンプし始めるのを見ることができます。 10 | 11 | しかし、実際には、次のような時間が必要になります。 12 | 13 | 14 | - コントローラをポーリングして、ジャンプボタンが押されていることを確認する 15 | - メモリ上のプレイヤースプライトを更新 16 | - 必要に応じて背景レイヤをスクロールさせる 17 | - アクションが起きたことを知らせるためのサウンドエフェクトの再生 18 | - 新しい位置にプレイヤーを配置した画面を再描画する 19 | 20 | ゲームにもよりますが、上記のことは通常、1フレームから4フレームの間で行われます。 21 | 22 | この待ち時間の大きな原因は、ゲームがVBlank割り込みの際に、ビデオフレームごとに1回しか入力状態をポーリングしないことにあります。 23 | 24 | 先読みの目的は、タイムシフトを使ってこれらの待機フレームをスキップすることです。 25 | 26 | ![](4.png) 27 | 28 | このように、ロックマンXでは、ジャンプボタンを押してからXがジャンプし始めるまでに3フレーム必要です。 29 | 30 | つまり、ユーザーが期待するの3フレーム目が描画されるまでに2フレーム分の内部処理の遅れがあるということです。そのため、先読み設定を1にすると、このうちの1フレームがスキップされ、2にすると、この2つの遅延フレームの両方がスキップされます。 31 | 32 | 3つ以上のフレームをスキップすると、アニメーションの開始フレームをスキップすることになり、非常に不快なラバーバンド的な視覚効果が発生します。 33 | 34 | その理由は、先読みの仕組みを説明したときにわかると思います。 35 | 36 | しかし、ここで強調しておきたいのは、ほぼすべてのスーパーファミコンのゲームには、少なくとも1フレームの内部処理の遅れがあり、1の設定(1フレーム分の先読み)は、0.1%程度のライブラリを除いて、すべてのゲームに有効だということです。 37 | 38 | 先読みの度合いを高くすると互換性がなくなりますが、一般的には1の設定なら体感的にはわかりません。 39 | 40 | このように、先読みは、スーファミのほぼ全てのライブラリにおいて、16〜20msの入力遅延を削るためのテクニックです。また、他の多くのシステムでも同じことが言えるでしょう。 41 | 42 | ## 技術的な説明 43 | 44 | 前述したように、先読みは時間をずらす技術です。まずは、標準的なエミュレータのランループを見てみましょう。 45 | 46 | ```c++ 47 | void Emulator::runFrame() { 48 | input.poll(); 49 | auto [videoFrame, audioFrames] = emulator.run(); 50 | video.output(videoFrame); 51 | audio.output(audioFrames); 52 | } 53 | ``` 54 | 55 | > この入力ポーリング戦略は最適ではありません。入力の遅延を減らすためのJITポーリング手法については、[こちら](latency.md)の記事を参照してください。 56 | 57 | 先読みを実装した場合、ランループのコードは次のようになります。 58 | 59 | ```c++ 60 | void Emulator::runFrameAhead(unsigned int runAhead) { 61 | if(runAhead == 0) return runFrame(); //sanity check 62 | 63 | //poll the input states of the controller buttons 64 | input.poll(); 65 | emulator.run(); 66 | //video and audio frames discarded (not sent to the monitor and speakers) 67 | 68 | //capture the system state so that we can restore it later 69 | auto saveState = emulator.serialize(); 70 | 71 | //we can run-ahead as many frames as we want 72 | while(runAhead > 1) { 73 | emulator.run(); 74 | //these frames are also discarded 75 | runAhead--; 76 | } 77 | 78 | //here we run the final frame 79 | auto [videoFrame, audioFrames] = emulator.run(); 80 | //the final frame is rendered 81 | video.output(videoFrame); 82 | audio.output(audioFrames); 83 | 84 | //lastly, we restore the save state we saved earlier 85 | emulator.unserialize(saveState); 86 | } 87 | ``` 88 | 89 | ここでは、先読みが2(フレーム分)だとします。上記のコードでは、コントローラの入力をポーリングして、3フレーム分をエミュレートしています。 90 | 91 | その結果、最後の3フレーム目だけが画面に表示されます。 92 | 93 | 変数`saveState`の目的は、3つのフレームを実行しても、1つのフレームを実行した後に前の状態をロードすることで、標準的な60fps(NTSC)または50fps(PAL)のゲームスピードレートを維持することです。 94 | 95 | 事実上、`runAhead=2` にしたときの結果は、ゲームパッドのボタンを2フレーム前に押したり離したりしていたらどうなっていたかを示すことになります。 96 | 97 | 実際、この手法はボタンを押しても離しても機能します。また、常に一定のフレーム数を未来に向けて表示しているため、ゲームの内部処理の遅延フレーム数を超えない限り、映像や音声が乱れることはありません。 98 | 99 | 内部処理の遅延フレーム数を超えると、アニメーションの冒頭や効果音の開始をスキップするようになり、かなり違和感があります。しかし、繰り返しになりますが、`runAhead=1` の設定は、基本的にほぼすべての場所で動作し、非常に効果的です。 100 | 101 | ## 実際の例 102 | 103 | ここでは、X軸がフレーム(0〜5)、Y軸が先読みフレームの数(0〜4)として、先読みによる効果を示す例をあげます。 104 | 105 | ![](1.png) 106 | 107 | 一番左のフレーム(#0)がアイドル状態を表していて,そのフレームが描かれた直後にジャンプボタンを押したとします。先読みを0に設定した場合(つまり先読みを使用しない場合)、Xがジャンプを開始するのは3フレーム後であることがわかります。 108 | 109 | 先読みを1にすると、最初の遅延フレームがスキップされるため、Xはわずか2フレームでジャンプを開始することができます。 110 | 111 | 先読みを2にすると、内部処理の遅延フレームが両方ともスキップされるため、Xは次のフレームですぐにジャンプを開始することができます。 112 | 113 | 先読みを3にすると、このゲームの場合、Xのジャンプの最初のアニメーションフレームが失われてしまいます。 114 | 115 | 先読みを4にすると、最初2つのアニメーションフレームがスキップされます。 116 | 117 | したがって、このゲーム(ロックマンX)では、先読みの設定を2にすると、NTSC版でXのジャンプの入力ラグが32ms減少しますが、影響はありません。 118 | 119 | ## (間違った)可視化例 120 | 121 | 先ほどとは別の方法でフレームを可視化してみました ...と言いたいですが、これは視覚的に便利になるだけで、技術的には先読みで起こっていることではありません。先読みは、入力状態の遷移だけでなく、常に未来のフレームを実行していることを覚えておいてください。 122 | 123 | ![](2.png) 124 | 125 | ## 正しい可視化例 126 | 127 | 実際の先読み時のゲーム映像は次のようになります。 128 | 129 | ![](3.png) 130 | 131 | 最初のレンダリングフレームでXがすぐにジャンプを開始しないのは、この時点ではコントローラのジャンプボタンが押されていないため、その入力がエミュレートされるように送り返されていないためです。 132 | 133 | そのため、上記の修正されたビジュアルデモンストレーションでは、最初のビジュアルフレームではジャンプボタンがリリースされ、それ以降のビジュアルフレームではすべてジャンプボタンが押されています。 134 | 135 | ## 先読みによるオーバーヘッド 136 | 137 | このテクニックは明らかにうまくいっているように見えますが、では何が問題なのでしょうか? 138 | 139 | それは主に、オーバーヘッドです。フレーム生成をマルチコアCPUに任せることはできません。なぜなら、各フレームは1つずつ順番にレンダリングされなければならないからです。つまり、シリアルプロセスなのです。 140 | 141 | つまり、先読みの設定が1の場合、スーパーファミコンのシステム全体を2回エミュレートしなければなりません。設定が2の場合は3回。そして4の設定では、スーパーファミコンを動かして、5フレーム分の映像・音声データを生成してから、1フレームだけを出力することになります。つまり、先読みを使わずにエミュレータを動かす場合の5倍のオーバーヘッドがあることになります。 142 | 143 | このオーバーヘッドを減らすための工夫があります。具体的には、フレームが画面に表示されないため、ビデオ生成のエミュレーションを行う必要がありません。つまり、フレームスキップと同じように扱うのです。ビデオはエミュレーションで最もコストのかかる部分のひとつであることが多いので、これにより先読みのパフォーマンスへの影響を大幅に軽減することができます。bsnesの場合、各フレームの先読みは、100%の追加オーバーヘッドに比べ、約40%の追加オーバーヘッドで済むことになります。 144 | 145 | 最近のbsnesの最適化により、エントリーレベルのRyzen CPUでも4フレームの先読みを簡単に処理できるようになりましたが、もちろん、エミュレータの要求レベルにもよります。 146 | 147 | ちなみに、エミュレータのターボ機能(ゲームの退屈な部分を高速化するためにフレームレートを制限せずに実行する機能)を使用する場合は、エミュレータが最大フレームレートを維持できるように、先読みを無効にする必要があります。 148 | 149 | ## 対戦型ゲームにおける先読みの使用について 150 | 151 | このことは、対戦型ゲームにおける先読みの使用に関して、重要な問題を提起しています。先読みは不正行為なのか? 152 | 153 | 入力の遅延を減らすことで、熟練したプレイヤーが有利になるのは確かです。 154 | 155 | 私の考えでは、先読みの使用とスキップするフレーム数が公開され、すべてのプレイヤーの間で一貫している限り、それは公平な競争の場となります。 156 | 157 | しかし、これを容認するか、不正行為と見なすかは、他の人の判断に委ねられます。 158 | 159 | 競争相手のいない一人でのプレイに関しては、それが問題になることはないと思いますが、もちろん人それぞれです。先読みは、エミュレーションの入力遅延を減らすことに興味がある人にとって、強力な選択肢のひとつに過ぎません。 160 | 161 | -------------------------------------------------------------------------------- /near/video/color-emulation/1_gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/1_gamma.png -------------------------------------------------------------------------------- /near/video/color-emulation/1_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/1_original.png -------------------------------------------------------------------------------- /near/video/color-emulation/2_gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/2_gamma.png -------------------------------------------------------------------------------- /near/video/color-emulation/2_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/2_original.png -------------------------------------------------------------------------------- /near/video/color-emulation/README.md: -------------------------------------------------------------------------------- 1 | # 色調エミュレーション 2 | 3 | ほとんどのレトロゲーム機は、何らかの形でRGBエンコーディングにより色を生成しています。 4 | 5 | しかし、生のピクセルカラーは、エミュレータが動作する画面とは全く異なる画面用に設計されていることが多いのです。この記事では、カラーエミュレーションの重要性を説明し、コードとスクリーンショットの例を紹介します。 6 | 7 | 最近のディスプレイは、液晶(LCD)パネルが主流です。これらは画面の後ろに光源があるため黒色の表現が非常に悪いことで知られています。液晶にはTN、PVA、IPSなどの種類がありますが、ここではそれらの違いはあまり関係ありません。 8 | 9 | マニアの間ではブラウン管が使われることもありますし、スマホやタブレットを中心に有機ELディスプレイも徐々に普及してきました。 10 | 11 | この記事では、主に液晶(LCD)に焦点を当てたテクニックを説明しますが、このテクニックはすべてのディスプレイタイプに重要です。 12 | 13 | ## 色精度 14 | 15 | まず第一に、ほとんどのコンピュータ向けのモニターは24bitカラーモードで動作します。 16 | 17 | つまり、赤(R)、緑(G)、青(B)の各チャンネルに8bit(0から255)の色空間が与えられています。例えば、R,G,Bを255(0xFF)にすると白(0xFFFFFF)を表現します。 18 | 19 | しかし、古いゲーム機の多くは、そのような精度で色を指定していません。 20 | 21 | 例えば、セガサターンは9bitカラーで、1チャンネルあたり3bitでエンコードされています。 22 | 23 | これを24bitカラーに直すにはさまざまな方法があります。 24 | 25 | 最も素朴なエミュレータであれば、3bitを出力の上位3bitに配置し、下位5bitをクリアにしてしまうかもしれませんが、そうすると白が少しグレーになってしまいます。 26 | 27 | 例: 28 | 29 | ``` 30 | 000 000 000 -> 000'00000 000'00000 000'00000 31 | 111 111 111 -> 111'00000 111'00000 111'00000 32 | ``` 33 | 34 | ![](graywhite.png) 35 | 36 | 逆に、下位5bitを全部1にすると、黒の場合は、少し明るくなってしまいます。 37 | 38 | 例: 39 | 40 | ``` 41 | 000 000 000 -> 000'11111 000'11111 000'11111 42 | 111 111 111 -> 111'11111 111'11111 111'11111 43 | ``` 44 | 45 | ![](grayblack.png) 46 | 47 | これを解決するには、ソースビット(元の3bit)がターゲットビットのすべてを埋めるように繰り返す必要があります。 48 | 49 | 例: 50 | 51 | ``` 52 | 000 -> 000 000 00... 53 | 010 -> 010 010 01... 54 | 011 -> 011 011 01... 55 | 111 -> 111 111 11... 56 | ``` 57 | 58 | コードで表すと 59 | 60 | ```c++ 61 | uint8 red = r << 5 | r << 2 | r >> 1 62 | //rrr00000 | 000rrr00 | 000000rr -> rrrrrrrr 63 | ``` 64 | 65 | ## 画面のエミュレーション 66 | 67 | レトロゲーム機は、現代のPC用液晶モニターで動作するようには設計されていません。 68 | 69 | 一般的に昔の家庭用ゲーム機はブラウン管TV(CRT)用に設計されています。ゲームボーイアドバンスなどの携帯型ゲーム機は現代のものよりかなり性能の悪い液晶パネルを使用しています。 70 | 71 | 画面の曲がり具合、スキャンライン、カラーブリード、フレーム間ブレンディング、アパーチャグリルなど、特定の画面のアーティファクトをエミュレートすることは、この記事の範囲外です。 72 | 73 | ### PC用液晶モニター 74 | 75 | 既存のモニターは、SRGBのような標準に合わせて専門的にキャリブレーションされているものは少ないので、色の範囲はかなり広いのですが、一般的には、適切にキャリブレーションされたSRGBモニターを使用しているかのように色をエミュレートしようとするのが最善の方法だと思います。 76 | 77 | ### ブラウン管TVのエミュレーション: SNES 78 | 79 | ブラウン管TVとPC用液晶モニターの大きな違いは、液晶では黒レベルが大幅に低下している(つまり黒が明るく見える)ことで、これはガンマ曲線を使ってわずかに補正するしかありません。 80 | 81 | ```c++ 82 | //SNES colors are in RGB555 format, so there are 32 levels for each channel 83 | static const uint8 gammaRamp[32] = { 84 | 0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c, 85 | 0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78, 86 | 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, 87 | 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff, 88 | }; 89 | ``` 90 | 91 | もともと、このテーブルは「Super Sleuth / Kindred」で有名なOverloadのものです。これは、カラーパレットの下半分を暗くし、上半分はそのままにしておくというものです。 92 | 93 | 左がオリジナル、右が上記のガンマランプを適用したもので、エミュレーションで出力される画像への影響は顕著です。 94 | 95 | 96 | 97 | 98 | 99 | ### 液晶のエミュレーション: GBA 100 | 101 | ゲームボーイアドバンスの液晶画面は、色強度が弱いと色が完全に消えてしまうというひどいものでした。 102 | 103 | 当時の賢い開発者は、色を大幅に誇張することで、実際のゲームボーイアドバンスの液晶画面でより良い結果を出せることを発見して実践していました。 104 | 105 | ただし、GBA用の色強度をそのままPCモニターに持ってくると、彩度が強すぎてしまいます。ありがたいことに、私たちはこれを補正して、むしろ自然な色を出すことができます。 106 | 107 | ```c++ 108 | double lcdGamma = 4.0, outGamma = 2.2; 109 | double lb = pow(B / 31.0, lcdGamma); 110 | double lg = pow(G / 31.0, lcdGamma); 111 | double lr = pow(R / 31.0, lcdGamma); 112 | r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); 113 | g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); 114 | b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); 115 | ``` 116 | 117 | このコードはTalarubi氏の提供によるものです。 118 | 119 | 左がオリジナル、右が色調補正したものですが、ブラウン管TV(とSNES)の例よりもはるかに劇的な効果が得られています。 120 | 121 | 122 | 123 | ### 液晶のエミュレーション: GBC 124 | 125 | ゲームボーイカラーの画面は意外と色再現性が高いので、色強度をそのままPCモニターに持ってきても少し彩度が強くなる程度で済んでいます。 126 | 127 | ゲームボーイカラーのエミュレーターでは、次のようなアルゴリズムがかなり好評です。 128 | 129 | ```c++ 130 | R = (r * 26 + g * 4 + b * 2); 131 | G = ( g * 24 + b * 8); 132 | B = (r * 6 + g * 4 + b * 22); 133 | R = min(960, R) >> 2; 134 | G = min(960, G) >> 2; 135 | B = min(960, B) >> 2; 136 | ``` 137 | 138 | 残念ながら、私はこのアルゴリズムを考案した人を知りません。ご存知の方は、ぜひご連絡ください。そうすれば、ここに適切なクレジットを加えることができます 139 | 140 | 前回同様、左がオリジナル、右が色調補正されたバージョンです。 141 | 142 | 143 | 144 | この例を選んだのは、前者の方が鮮やかで好ましいのですが、よく見るとキャラクターのスプライトの周りに市松模様があり、背景よりも明るくなっています。 145 | 146 | これは開発者の配慮によるもので、実際のゲームボーイカラーでは、白が洗い流され、2つの異なる色調がほとんど違和感なく混ざり合っています。 147 | 148 | ## 終わりに 149 | 150 | 現在、優れた色調補正フィルターを持たないシステムは他にもたくさんあります。微調整が非常に難しいのです。特に、ワンダースワンやネオジオポケットの色を近似させるフィルターは、本稿執筆時点ではありません。 151 | 152 | 携帯ゲーム機の場合、バックライト(場合によってはフロントライトも)がないことが多く、また、コントラスト比を調整するためのシステム制御があるため、RGBの値に対して真の「色」の値はひとつではありませんから、さらに厄介です。 153 | 154 | 特に面白いのはワンダースワンカラーで、ソフトウェアでフラグを立てて、画像出力時にコントラストを強くすることができます。これを正確にエミュレートするにはどうしたらいいのか、本当にできるのか、今のところ不明です。 155 | 156 | 色調エミュレーションはもっと注目されてもいい分野なので、数学や色の分析が得意な方は、ぜひエミュレーションの現場に参加してみてください。 157 | -------------------------------------------------------------------------------- /near/video/color-emulation/gba_lcd_gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/gba_lcd_gamma.png -------------------------------------------------------------------------------- /near/video/color-emulation/gba_lcd_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/gba_lcd_original.png -------------------------------------------------------------------------------- /near/video/color-emulation/gbc_lcd_gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/gbc_lcd_gamma.png -------------------------------------------------------------------------------- /near/video/color-emulation/gbc_lcd_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/gbc_lcd_original.png -------------------------------------------------------------------------------- /near/video/color-emulation/grayblack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/grayblack.png -------------------------------------------------------------------------------- /near/video/color-emulation/graywhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/near/video/color-emulation/graywhite.png -------------------------------------------------------------------------------- /others/GBZ80Opcodes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/others/GBZ80Opcodes.pdf -------------------------------------------------------------------------------- /others/SM83_decoding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/others/SM83_decoding.pdf -------------------------------------------------------------------------------- /others/ThumbRefV2-beta.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/others/ThumbRefV2-beta.pdf -------------------------------------------------------------------------------- /others/armref.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/others/armref.pdf -------------------------------------------------------------------------------- /others/netplay.md: -------------------------------------------------------------------------------- 1 | # Discord メモ 2 | 3 | https://discord.com/channels/465585922579103744/466418871210082325/928542950814810153 4 | 5 | 通信ケーブルは、ロックステップや巻き戻しによって最も正確にエミュレートされることが多いのですが、これは恐ろしいことです。例えば、ゲームボーイなどでは、オリジナルのシリアルプロトコルが毎回片道1バイトを送信するため、転送のたびに通信の返信を期待するゲームがあります。 6 | 7 | また、ゲームのプロトコルを直接ハイジャックして、ネット対戦の余計な遅延を無視できるような、(テトリスのオンラインプロジェクトのように)明確な「通信不可」バイトを持つものもあります。 8 | 9 | しかし、双方向転送の結果、99.9%のゲームボーイなどのゲームのエミュレーションは、以下のいずれかの方法で行われています。 10 | 11 | 1. ホスト上で両方のゲームを完全にロックステップで実行し、2番目のプレーヤーからの入力をホストに送信し、クライアント上でゲームのステートを再構築するのに十分な量を送信します。常に正確ですが、プレイヤー2には入力の遅延が発生します。ちなみに、これはopenttd(PCゲーム)のマルチプレイヤーの仕組みです。 12 | 2. ホストとクライアントをほぼ同期して動作させることで、ホストとクライアントは1つのシリアル転送の時間以上に先に進むことができず、どのバイトがいつ転送を開始したかを相手が教えてくれるのを待ちます。これは低レイテンシーを必要とするため、確かLAN上でよりよく機能します。技術的には、転送が部分的に完了したときに、転送レジスタの正しい状態を持たないので、正確性に欠けますが、これはおそらく問題ないでしょう。 13 | 3. 転送が入ってきたときに各エミュレータを巻き戻し、転送すべきだった時点から再実行する。これにより、両方のプレーヤーが過去の正しい時点で転送を考慮しながら、入力の遅延なしにローカルで「最新」の状態にすることができます。ただしより多くの処理が必要です。この方法は最も先進的なもので、オンラインPCゲームの遅延補正に似ています。 14 | 15 | -------------------------------------------------------------------------------- /others/w65c816s.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akatsuki105/emu-docs-ja/42eae8b719208badd58ea28511e36ee06acf1a82/others/w65c816s.pdf -------------------------------------------------------------------------------- /preparing-your-game-for-deterministic-netcode.md: -------------------------------------------------------------------------------- 1 | # 決定論的ネットコード 2 | 3 | >**Note** 4 | > この記事は [Preparing your game for deterministic netcode](https://yal.cc/preparing-your-game-for-deterministic-netcode/) を翻訳したものです。 5 | 6 | >**Note** 7 | > ネットコード: ネットプレイ(インターネットを使ったマルチプレイ)を実現するプログラム 8 | 9 | 決定論的ネットコードとは、**各ゲームクライアントが、フレームごとに同じ初期状態と入力を与えられたときに、同じ状態になること**を意味します。 10 | 11 | 一般的な決定論的ネットコードには、ロックステップやロールバックなどがあります。 12 | 13 | ## ロックステップ 14 | 15 | ロックステップは決定論的プロトコルの比較的シンプルな実装で、各プレイヤーの入力/アクションがそのフレームで判明した場合にのみ、次のゲームフレームが処理されるようになっています。 16 | 17 | ロックステップベースのネットプレイでは、リモート入力が時間通りに届くように、RTTの中央値の半分の入力遅延を加える必要があります。 18 | 19 | また、フレームに対するすべてのリモート入力がわかるまでゲームを停止する必要があるため、モバイルやNintendo Switchなどの接続が不安定なデバイスでプレイするゲームには最適ではありません。 20 | 21 | ## ロールバック 22 | 23 | ロールバック は、ロックステップ を改良したもので、遠隔地(相手プレイヤー)からの入力が間に合わなかった場合に、プレイヤーが入力を推測します。その後、入力が判明した時点でゲームを巻き戻し、修正した入力でゲームフレームを再実行します。 24 | 25 | つまり、100msの長さの接続障害が発生した場合、リモートプレイヤーはその6フレームほどの間、すでに保持していた入力を保持し続け、実際の入力が到着した時点で修正し、視覚的にはキャラクターが新しい場所に一時的に適応するだけであると仮定することができます。 26 | 27 | もちろん、予測されるフレーム数が多ければ多いほど、状態が修正された後に何かが全く違う方向に進んでしまう可能性が高くなります。数フレームならまだしも、1秒分の入力を予測すると、相手のキャラクターがまったく別の場所に移動してしまう可能性が高くなります。そのため、対戦ゲームでは、ゲームがロックステップのような失速をするまでの予測フレーム数に上限を設ける傾向があります。 28 | 29 | ## メリット 30 | 31 | - 低速なネット環境でも問題なく動作 32 | - 入力データを送るだけだからです。 33 | - プレイヤー間の有利不利が比較的小さい 34 | - ホストが他のプレーヤーより優位に立つことはなく、ホストに近いプレーヤーも優位に立つことはありません。 35 | - ゲームプログラムとネットコードが分離できる 36 | - つまり、例えば格闘ゲームに追加の技を加える場合、通常はネットコードを触る必要がないため、ゲーム開発とネットコードを別々の人が担当するチームでは、やりとりの回数を減らすことができます。 37 | 38 | ## デメリット 39 | 40 | - 入力遅延 41 | ロールバックで解決できますが、遅延をRTTの半分の中央値よりも低く設定すると、リモートのプレイヤーが一貫して予測を誤るため、常にグリッチが発生してしまいます。それはともかく、多くの人は入力遅延よりもそちらを好みます。 42 | - スケーラビリティ 43 | - 各プレイヤーは、自分の入力を他のプレイヤーに送信する必要があります。言うまでもなく、接続数が多ければ多いほど、特定のペアのプレーヤーがお互いに接続不良となり、他のプレーヤーに問題を引き起こす確率が高くなります。 44 | - 一般的に決定論的ネットコードは、4人以上のプレイヤーがいるゲームでは、メッシュトポロジーと組み合わせて使用することはありません。入力遅延が少ないゲーム(RTSゲームなど)では、スター型トポロジー(全員がホストに接続)を使用することができます。 45 | - チート 46 | - 各プレイヤーはゲームの状態を常に把握しているので、見えてはいけない情報を見てしまうこともあります。 47 | - テンポの速いゲームでは、それはあまり気になりません。例えば、格闘ゲームでは、自分と相手の見え方に違いがあることはほとんどなく、推測できる余分な情報は、多くのプレイヤーがすでに暗記しているヒットボックスやクールダウンだけです。 48 | - 同期ずれ 49 | - もしネットコードが決定論的でない場合は問題が生じます。例えば、グローバル変数が設定値に達したときにインスタンスを生成し、セッション開始時にリセットすることを忘れています。これにより、プレイヤーが同じゲーム状態を見ることができなくなり、ステートのずれが発生する可能性があります。 50 | - ゲームの状態を再同期させようとすると、計算コストがかかり、かなりの帯域幅を必要とします。また、ゲームが再び同期ずれを起こさないとは限らず、ゲームの状態を繰り返し再同期するという死のループに陥る可能性があります。 51 | - 同期ずれのデバッグは、決定論的ネットコードを扱う上でより複雑な部分の一つであり、ゲームステートダンプ/ログの比較が必要です。 52 | 53 | ## 注意 54 | 55 | ### ネット環境の重要性 56 | 57 | パケットが遅れると、ストール(ロックステップ)や映像の不具合(ロールバック)が発生するため、プレイヤー間の接続をできるだけ良好に保つことが何よりも重要になります。 58 | 59 | パケットロスを軽減する技術は必須であり、最近のゲームでは、プライベートネットワークを介してトラフィックをルーティングする傾向が強まっています。これは通常のP2Pよりもレイテンシや安定性が向上する場合があります 60 | 61 | ### ツール特有の問題 62 | 63 | ゲームエンジンやフレームワークの中には、決定論的性質よりもパフォーマンスを優先するコンポーネントが組み込まれているため、本質的に決定論的ネットコードに適していないものがありますが、これはソースコードにアクセスできたとしても、簡単に対処できる問題ではありません。 64 | 65 | 例えば、GameMakerには比較のためのイプシロンが組み込まれており(近い数は等しいとみなされる)、また、コリジョンチェック関数が歴史的経緯から座標を丸めるように組み込まれているため、小さな浮動小数点のエラーは全く気づかれないという点で、この点に関しては比較的良い状況にあります。 66 | 67 | 一方、Unity は決定論的ネットコードには適していません。というのも、ビルトインAPI のほとんど(物理演算を含む)が決定論的ではなく、決定論を求める場合は、そのかなりの部分を固定小数点構造体で再実装することになるからです。 68 | 69 | ### 可変フレームレート 70 | 71 | ゲームロジックはプレイヤー間で同じペースで進行する必要があり、ビジュアルは補間/外挿する必要があるため、様々な画面のリフレッシュレートでゲームを動作させることは、決定論的ネットコードではより困難です。 72 | 73 | ## どのようなゲームが決定論的ネットコードを必要とするのか? 74 | 75 | ### ゲームスピードの速い対戦ゲーム 76 | 77 | 例えば、格闘ゲームやプラットフォーマーゲームでは、ほとんどのゲームでロールバックやロックステップのネットコードが採用されています。基本的にはロールバックが推奨されます。 78 | 79 | ### ゲームスピードの速い協力型ゲーム 80 | 81 | 一般的に、厳密な協力プレイを行うゲームであれば、従来のクライアント・サーバーモデルを採用し、可能な限りプレイヤーを優遇することができますが、協力モードと対戦モードの両方を備えたゲームや、高い精度が要求されるゲームでは、ロールバックネットコードを利用することができます。 82 | 83 | 最近の例では、「Spelunky 2」が最も有名ですが、ロールバックネットコードは、高予算のベルトスクロールアクションゲームにも見られます。 84 | 85 | ### RTSゲーム 86 | 87 | ゲーム内で何百ものユニットが動き回っている場合、それらのユニットに関する情報を効果的に同期させることは困難であり、これがRTSゲームが歴史的にロックステップに傾いてきた理由です。 88 | 89 | 2021年現在、インターネットの速度の中央値は一般的に十分な速度であり、多くのRTSゲームはロックステップの代わりにクライアント・サーバーモデルを使用することができるようになりました。クライアント・サーバーモデルの導入はいくつかの不正行為の解決策にもなりました。 90 | 91 | ### エミュレータ 92 | 93 | 各ゲームのROMにネットワークロジックを組み込むことは、一般的には不可能です。また、エミュレータは本質的に決定論的であるため、決定論的ネットコードに適しています。 94 | 95 | ## ネットコード実装の準備 96 | 97 | プレイの快適さに応じて変わってきます。 98 | 99 | ### Tier 0: 前準備 100 | 101 | これらは、ネットコードを実装するかどうかわからなくても、やっておいたほうがいいと思います。 102 | 103 | - ゲームにオンラインのマルチプレイを搭載する場合は、何らかの形でローカルネットワーク上のマルチプレイを動作させる必要があります。ゲームのあらゆる部分をマルチプレイに対応させるには時間がかかり、大作ゲームでは極端な場合、ネットコードを実装するよりもゲームを一から作り直したほうが安くつくこともあります。 104 | - データの保存先と読み込み先、ゲームの状態に影響を与えるものなどを記録しておきましょう。例えば、プレイヤーが何かをアンロックした後にのみ、レベル上の特定のエンティティが出現する場合、マルチプレイヤーではその事実を同期させる必要があります。 105 | 106 | ### Tier 1: デスクトップPC上のロックステップ 107 | 108 | 必要なのは 109 | 110 | - Tier0の前準備を終えている 111 | - 入力ポーリングを`button_check(player_index, button_index)`に抽象化するか、あるいは各入力の状態を示す変数をどこかに割り当てるかして、一箇所にまとめるようにします。 112 | 113 | 具体的な例としては、ゲームにリプレイシステムを導入してみましょう。 114 | 115 | リプレイとは、初期状態(ゲームプレイに影響を与える設定やアンロックなど)を含むファイルであり、マッチ/セッション開始以降のフレームごとのプレイヤーの入力内容を含みます。 116 | 117 | リプレイでは、初期状態を適用し、デバイスをポーリングするのではなく、各フレームの入力をファイルから取得することで、ゲームを再生することができます。 118 | 119 | 同期ずれをせずにリプレイを動作させることができれば、それでいいと思います。 120 | 121 | ### Tier 2: Web/モバイル/ゲーム機上のロックステップ 122 | 123 | ロックステップなのはTier1と同じですが、次の違いがあります。 124 | 125 | デスクトッププラットフォームでは、ネットワークAPIは一般的に同期バージョンの関数を持っています。つまり、ゲームを一時的に停止させる必要がある場合は、データが利用可能になるまでソケット/APIを繰り返しポーリングすることで可能になります。 126 | 127 | 一方、他のプラットフォームでは、同期ポーリングは推奨されない場合もあれば、不可能な場合もあります。(特にHTML5では不可能です) 128 | 129 | なので、ここでは以下のことが必要になってきます。 130 | 131 | - Tier0とTier1を実装済み 132 | - 実際のフレームに対して、任意の数(ゼロを含む)のゲームロジックのフレームを処理できるようにします。 133 | - これは通常、ゲームロジックのコードを別の場所に移動して、必要に応じて簡単に呼び出せるようにすることで実現できます。例えば、GameMakerでは`Step`イベントのコードを`User Event`に移動したり、Unityでは`Update/FixedUpdate`を自分で定義した関数に移動したりします。 134 | - また、あなたが使っているゲームエンジンで自動的に処理されるロジックも処理しなければならないことに注意してください。 135 | 136 | Tier2の内容が実装できると、具体的には、従来のリプレイシステムに一時停止や早送り(2倍速)の機能を実装することができるようになります。 137 | 138 | ### Tier 3: ロールバック 139 | 140 | 必要なのは 141 | 142 | - Tier2までを全て実装済み 143 | - オンデマンドなゲームステートのセーブ・ロードを実装 144 | - これは、ゲームステート(ゲームプレイに影響を与えるすべてのもの)全体をシリアライズ/デシリアライズして、後から読めるようなフォーマットにしなければなりません。慣例的にはバイナリ形式のシリアライズですが、技術的には十分な速度が出れば何をしても大丈夫です。 145 | - 実装の難しさは、ゲーム本体の数、データの量、使えるツールの種類などによって、ゲームやエンジンごとに大きく異なります。 146 | 147 | 具体的な例としては、先に作られたリプレイシステムに、あるタイミングでのセーブ/ロード機能を実装する必要があります。 148 | 149 | セーブとは、ゲームの状態と現在のファイルの読み取りタイミングを保存することです。 150 | 151 | ロードとは、ゲームの状態を読み込み、ファイルの読み取りタイミングを先に保存したものにリセットすることです。 152 | 153 | これらにより効果的にリプレイを巻き戻すことができます。 154 | 155 | ## どのくらいの時期にネットコードを実装すべきか? 156 | 157 | 理想的には早ければ早いほどいいのですが、ゲームの制作には時間がかかりますし、ゲームのコードベースは開発中に大きく変化することがありますので、ネットコードの準備はできていても、実際にネットコードに着手するのはゲームの機能が完成に近づいてからということもよくあります。 158 | 159 | 初めてネットコードを実装する場合は、問題が発生する可能性があるため、最低でも数ヶ月の余裕を持ってテストとデバッグを行うようにしてください。 160 | 161 | 162 | --------------------------------------------------------------------------------