Open12

bsnes ソースコードリーディング

Koki OyatsuKoki Oyatsu

bsnes のソースコードを読んだ記録をしていく

  • なんで読むの?
    • SNES エミュレータ実装で拡張チップ SA-1 は実装してなかったから。CX 系も知れたら、くらいか
  • なんで higan じゃないの?
    • スーファミ以外のコードが入ってくるとコードパスがわかりにくいような予感がするから
  • どこまで読む?
    • 多分 main から各コンポーネント (CPU、PPU、SMP、DSP) の呼び出し場所
    • nall, ruby など bsnes 上に構築されたコンポーネントも読み解く
Koki OyatsuKoki Oyatsu

コードと直接関係ありそうなところを抜き出したディレクトリ構成

.
├── bsnes
├── docs
├── extras
├── hiro
├── libco
├── nall
├── ruby
├── shaders
└── sourcery
Koki OyatsuKoki Oyatsu

まずはひと目でわかるところ

  • bsnes
    • bsnes source code
    • README.md がある
  • docs
    • ドキュメント類があるが、Manifest (ゲームのメタ情報) 上書き方法が書いてある感じ
  • extras
    • settings.bml のみ。bsnes の設定保存場所かな
Koki OyatsuKoki Oyatsu

ここから下が bsnes 独特な場所。higan の wiki から持ってくる

hiro

原文

hiro is the cross-platform GUI toolkit that higan uses. Unlike other cross-platfrom GUI toolkits, it uses native widgets on each supported platform: GTK+ or Qt on Linux/BSD, Cocoa on macOS, and the standard controls on Windows.

Depends on:
- nall

deepl

hiroは、higanが使用するクロスプラットフォームGUIツールキットです。他のクロスプラットフォームGUIツールキットとは異なり、サポートされる各プラットフォームのネイティブウィジェットを使用します。Linux/BSDではGTK+やQt、macOSではCocoa、Windowsでは標準のコントロールが使用されます。

クロスプラットフォームGUIを担保するライブラリ。とはいえ、各プラットフォームに向けた記述がされているのでボリュームは多め。

mac だと cocoa ディレクトリ配下あたりがスタートかな

libco

原文

libco is a cooperative multithreading library, which allows higan's code to describe each system component separately, but to run nearly as fast as if every component were woven together.

deepl 翻訳

libco は協調型マルチスレッドライブラリで、higan のコードが各システムコンポーネントを個別に記述しながら、各コンポーネントが織りなすようにほぼ高速に動作することを可能にしています。

「cooperative multithreading」のところにリンクが張ってあるが byuu.org はリンク切れなので、internet archive を漁る

https://web.archive.org/web/20201111192602/https://byuu.net/design/cooperative-threading/

ぐわー、すごい量書いてる。一旦後回し

nall

原文

nall is byuu's alternative to the C++ standard library. As well as providing standard functionality like string processing and data structures, it also provides code shared between byuu's various projects, like cryptography algorithms, audio and image processing, and cross-platform filesystem handling.

deepl 翻訳

nall は byuu の C++ 標準ライブラリの代替となるものです。文字列処理やデータ構造のような標準的な機能を提供するだけでなく、暗号アルゴリズムや音声・画像処理、クロスプラットフォームのファイルシステム処理のような、byuuの様々なプロジェクトで共有されるコードも提供しています。

ふむ、utils 的なやつ。HTTP client/server とかもあったりする。これも量が凄い

ruby

原文

ruby is the interface between a hiro application and platform-specific APIs for emulator output and input. It displays emulated video through OpenGL or Direct3D with shaders; it plays emulated audio through ALSA, OSS, or WASAPI; and it reads gamepad, keyboard and mouse input.

Depends on:
- hiro
- nall

deepl 翻訳

rubyは、hiroアプリケーションと、エミュレータの出力や入力のためのプラットフォーム固有のAPIとの間のインターフェイスです。OpenGLやDirect3Dのシェーダーによるエミュレーション映像の表示、ALSAやOSS、WASAPIによるエミュレーション音声の再生、ゲームパッドやキーボード、マウスの入力の読み込みを行います。

video, audio, input というディレクトリがある。GUI と責務を分けたのは確かに。

shaders

これは higan のドキュメントにはないが、まんまシェーダーが入っているところ

sourcery

これも higan のドキュメントにはない。

sourcery.cpp を見た感じ、bml を input として、hpp と cpp を生成しているように見える。ビルド周りで使うのかな?

Koki OyatsuKoki Oyatsu

ビルドスクリプト

何はともあれ大事なビルドスクリプト
ビルドする際にはこういうコマンドでやる (mac 想定)

git clone git@github.com:bsnes-emu/bsnes.git
# clang: error: the clang compiler does not support '-march=native' が出るので local=false つける
make -C bsnes local=false

GitHub Actions workflow に build.yml があって助かった
build.yml

ビルドログはこんな感じ。めちゃくちゃ Makefile キレイな気がする

make 時のビルドログ

Compiling hiro/hiro.cpp ...
Compiling ../ruby/ruby.cpp ...
Compiling ../libco/libco.c ...
Compiling emulator/emulator.cpp ...
Compiling filter/filter.cpp ...
Compiling lzma/lzma.cpp ...
Compiling sfc/interface/interface.cpp ...
Compiling sfc/system/system.cpp ...
Compiling sfc/controller/controller.cpp ...
Compiling sfc/cartridge/cartridge.cpp ...
Compiling sfc/memory/memory.cpp ...
Compiling sfc/cpu/cpu.cpp ...
Compiling sfc/smp/smp.cpp ...
Compiling sfc/dsp/dsp.cpp ...
Compiling sfc/ppu/ppu.cpp ...
Compiling sfc/ppu-fast/ppu.cpp ...
Compiling sfc/expansion/expansion.cpp ...
Compiling sfc/coprocessor/coprocessor.cpp ...
Compiling sfc/slot/slot.cpp ...
Compiling gb/Core/apu.c ...
Compiling gb/Core/camera.c ...
Compiling gb/Core/rumble.c ...
Compiling gb/Core/display.c ...
Compiling gb/Core/gb.c ...
Compiling gb/Core/joypad.c ...
Compiling gb/Core/mbc.c ...
Compiling gb/Core/memory.c ...
Compiling gb/Core/printer.c ...
Compiling gb/Core/random.c ...
Compiling gb/Core/rewind.c ...
Compiling gb/Core/save_state.c ...
Compiling gb/Core/sgb.c ...
Compiling gb/Core/sm83_cpu.c ...
Compiling gb/Core/symbol_hash.c ...
Compiling gb/Core/timing.c ...
Compiling processor/arm7tdmi/arm7tdmi.cpp ...
Compiling processor/spc700/spc700.cpp ...
Compiling processor/wdc65816/wdc65816.cpp ...
Compiling target-bsnes/bsnes.cpp ...
Compiling target-bsnes/program/program.cpp ...
Compiling target-bsnes/input/input.cpp ...
Compiling target-bsnes/presentation/presentation.cpp ...
Compiling target-bsnes/settings/settings.cpp ...
Compiling target-bsnes/tools/tools.cpp ...
Compiling target-bsnes/resource/resource.cpp ...
Linking out/bsnes ...
rm -rf out/bsnes.app
mkdir -p out/bsnes.app/Contents/MacOS/
mkdir -p out/bsnes.app/Contents/MacOS/Database/
mkdir -p out/bsnes.app/Contents/MacOS/Firmware/
mkdir -p out/bsnes.app/Contents/MacOS/Shaders/
mkdir -p out/bsnes.app/Contents/Resources/
mv out/bsnes out/bsnes.app/Contents/MacOS/bsnes
cp Database/* out/bsnes.app/Contents/MacOS/Database/
cp -r ../shaders/* out/bsnes.app/Contents/MacOS/Shaders/
cp target-bsnes/resource/bsnes.plist out/bsnes.app/Contents/Info.plist
sips -s format icns target-bsnes/resource/bsnes.png --out out/bsnes.app/Contents/Resources/bsnes.icns
/Users/xxxxx/tmp/bsnes/bsnes/target-bsnes/resource/bsnes.png
/Users/xxxxx/tmp/bsnes/bsnes/out/bsnes.app/Contents/Resources/bsnes.icns

これらのコードを起点に読んでいけばいつかたどり着くかな

Koki OyatsuKoki Oyatsu

main エントリ的な場所

bsnes/target-bsnes/bsnes.cpp
auto nall::main(Arguments arguments) の箇所

  • new SuperFamicom::Interface の呼び出し
    • bsnes/sfc/interface/interface.hpp に定義されている
    • Emulator::Interface を継承
    • bsnes/emulator/interface.hpp に定義がある
    • ここでいう interface はプログラミング的なものより UI とかに近いニュアンス
  • program.create(); を呼び出し

bsnes/target-bsnes/program/program.cpp

  • 各種Settings の作成、Application::onMain({&Program::main, this});onMain にコールバック仕掛ける

bsnes/target-bsnes/bsnes.cpp に戻り、Application::run の実行

hiro/cocoa/application.cpp
auto pApplication::run() が発火

Koki OyatsuKoki Oyatsu

bsnes/target-bsnes/program/program.cpp

  • Application::onMain({&Program::main, this}); からの Program::main
  • emulator->run() が呼び出されているのが分かる
  • emulator は先述した中身は new SuperFamicom::Interface したもの
  • bsnes/sfc/interface/interface.cpp にある Interface::run()system.run() を呼び出している
  • system.run() ?
Koki OyatsuKoki Oyatsu

bsnes/sfc/system/system.cpp

  • SuperFamicom::system。この Run では scheduler mode を Run に変えて、scheduler.enter()

scheduler は bsnes/sfc/sfc.hpp

  • struct Schedulerauto enter() がある
  • co_active() の呼び出しがある
  • libco.h に定義がある

ここは Thread だな

Koki OyatsuKoki Oyatsu

bsnes/target-bsnes/presentation/presentation.cpp

ROM ファイルが drop されたときには Presentation::onDrop が呼ばれる

その中で program.load()

bsnes/target-libretro/program.cppProgram::load() の中で emulator->load() の呼び出し

ちょっと飛ばすが System::load が呼ばれて、各種コンポーネントの load() 実行

  • cpu
  • smp
  • ppu
  • dsp
  • cartridge

cartridge の region に応じて CPU Frequency が変わる

  if(cartridge.region() == "NTSC") {
    information.region = Region::NTSC;
    information.cpuFrequency = Emulator::Constants::Colorburst::NTSC * 6.0;
  }
  if(cartridge.region() == "PAL") {
    information.region = Region::PAL;
    information.cpuFrequency = Emulator::Constants::Colorburst::PAL * 4.8;
  }

Colorburst は bsnes/emulator/emulator.hpp に定義されている

Program::load() に戻り、ゲームに応じた Hacks がある

emulator->power()

bsnes/sfc/interface/interface.cppInterface::power に行き、system.power() なので、
bsnes/sfc/system/system.cppSystem::power(bool reset)

ここで各コンポーネントの power(reset) 呼び出し

CPU の例

auto CPU::power(bool reset) -> void {
  WDC65816::power();
  Thread::create(Enter, system.cpuFrequency());
  coprocessors.reset();
  PPUcounter::reset();
  PPUcounter::scanline = {&CPU::scanline, this};
  // (snip)
}

拡張チップもここで power() 呼び出し

今回は SA1 に興味があるので SA1 を見る

auto SA1::power() -> void {
  double overclock = max(1.0, min(4.0, configuration.hacks.sa1.overclock / 100.0));

  WDC65816::power();
  create(SA1::Enter, system.cpuFrequency() * overclock);

WDC65816 を 2 個持つ感じなのね。SA1 は動作周波数が違う感じ

create は bsnes/sfc/sfc.hpp にある

    auto create(auto (*entrypoint)() -> void, uint frequency_) -> void {
      if(!thread) {
        thread = co_create(Thread::Size, entrypoint);
      } else {
        thread = co_derive(thread, Thread::Size, entrypoint);
      }
      frequency = frequency_;
      clock = 0;
    }
Koki OyatsuKoki Oyatsu

System::power(bool reset) の続き

scheduler.active = cpu.thread;

で scheduler の active を cpu.thread に指定。これで大体の準備はおしまい

各種 CPU, PPU などが先述した co_create に指定されている ::Enter() を呼び出す

内部では cpu.main() などの呼び出し

auto CPU::Enter() -> void {
  while(true) {
    scheduler.synchronize();
    cpu.main();
  }
}

scheduler.synchronize でタイミングを合わせていると思われるが、bsnes/sfc/sfc.hpp の frequency との兼ね合いもちょっと謎なのよね

Koki OyatsuKoki Oyatsu

60 fps ぴったりに制御している場所

60fps ぴったりにしているところがわからなくて苦戦してた。設定でデフォルト有効がゆえか〜

https://twitter.com/kaishuu0123/status/1567852645045649412

bsnes でちゃんと 60 fps になっている理由がやっと分かった( ˊᵕˋ ; ) Audio の Synchronize が On になってるからで、これを切ったら百数 fps で動き出したので納得。
Synchronize 設定で律速してるのね。コードだけ読むんじゃなくて触ってみないと気づかない典型

これでなんとなく分かった。

https://web.archive.org/web/20200322151954/https://byuu.net/design/schedulers

byuu さんの scheduler に書かれている。bsnes は Relative Schedulers の方。

しかし、 cooperative multithreading といい、byuu さんは凄いなぁ。

ここ以降は各コンポーネントに派生して読んでいけそうなので一旦 close

higan のおすすめもされたし。