today 2019-09-17
access_time 3 mins
進捗ツイートまとめ
ここ一ヶ月ぐらいRust入門を兼ねて、ファミコンエミュレータを自作している。変遷についてまとめたかったのだがTwitterのモーメントがもう使わせる気がなさそうなのでブログにまとめておく。
誰がなんというとこれはモーメント。
筆者の背景を紹介するが、単純に書いた量と馴染みで言えばC#, C/C++, JavaScript/TypeScript, Scalaの4つだと思う。
特定の言語を持ち上げたりすることはあまり得意ではないが、2015年頃から漠然と安全側に傾けた言語に興味が湧くようになっていた。書くと長くなるがscalaを書き始めたことと、LLVMについて調べだしたあたりが強く影響している。
その中でも組み込み開発での可用性が高そうで、パフォーマンスも既存の言語に引けを取らないという点でRustが私の中で候補に上がっている。(golangはOSレスno_stdな組込み向けではTinyGo次第なところがある)
NesDev wiki が圧倒的な情報量を取り揃えている。幸いファミコンエミュレータについては日本でも作っている先輩方が多数おられるので、英語が苦手でも日本語情報がかなり得られるので照らしながら作ると良い。
細かい実装の話などは後日別の媒体にまとめようと思っているので、残りは実装の変遷を。
はじめはwikiにあるCPU命令をすべて実装する作業を行った。きっと合っているだろうという気持ちで…。
macro完全に理解した(何もわからない pic.twitter.com/RD0TvtNuuJ
— kamiya (@kamiya_owl) August 17, 2019
すぐに言語マクロを試したくなるのは趣味。
わーい、とりあえずRESET割り込みで命令先頭に飛んできてはじまるようになった pic.twitter.com/EdhqazRvz4
— kamiya (@kamiya_owl) August 18, 2019
CPU命令と.nes形式ファイルの展開を実装した頃で、実機起動時(もしくはResetボタン相当)の動きができるようになった。
Reset割り込みは0xfffc, 0xfffdに記されたアドレスに飛ぶような挙動なので、エントリポイントに飛ばせるようになったことに等しい。ここがスタート地点だ…?
NES研究所 様が公開しているHello, Worldを表示するサンプルを動かすことにした。対戦よろしくお願いします。
rust emuの終了時レジスタを使った簡易リグレッションテスト、いいね pic.twitter.com/E5nlVsvX8l
— kamiya (@kamiya_owl) August 20, 2019
バイナリエディタとオペコード一覧を見て、命令のフェッチがうまく行っているか見ながらステップ実行を繰り返している様子を眺めていた。
一部修正してPPUADDR, PPUDATA経由でCPUからBGの情報と”Hello, World!”の文字列をを流しているのがおおよそ確認できたので次に進むことにした。
rust-nes-emulator Issue #3: Hello Worldの実行結果の正当性を確認する
ファミコンにはCPUの他にPPU(Picture Processing Unit), APU(Audio Processing Unit)というサブシステムが乗っかっており、画面表示はこのPPUが担っている。
表示できるものにはいわゆる背景のBGと、マリオとかクリボーみたいに動かせるSpriteがある。Hello, WorldのサンプルではSpriteは使っていなかったのでBGを描画するコードの実装した。
やったー!rustで書いてるファミコンエミュでHello Worldが出せるようになった~~~ pic.twitter.com/VzdFz14g4x
— kamiya (@kamiya_owl) August 25, 2019
頭がオカシイのか何故かここだけは一発で動いてしまった、多分ここで苦戦してたらエミュ自作にこれほどはまらなかったかもしれない…。
GUIでグイグイ pic.twitter.com/sp4heMW98p
— kamiya (@kamiya_owl) August 25, 2019
最初はbitmap画像にしていたが不便だったので、piston_windowというライブラリを使って表示制御することにした。
ギャア(アスタリスクしかちゃんと出てねぇ pic.twitter.com/oOk3VuU1k3
— kamiya (@kamiya_owl) August 26, 2019
残念ながらこの時点では他のROMはほぼ動かなかった。nestestでさえこの有様。Hello, Worldがいかに簡潔なサンプルなのか思い知らされる。
画像は省略します。nestest.nesという命令一式を試せる素晴らしいROMがあるのですが先の通り表示すらできない有様。
nestestの作者はこれはもう素晴らしくて、画面が表示できなくてもPCを0xC000に飛ばせば画面なくてもテストが進むよモードを実装していた…天才か。
println散らかるし入れまくると遅いしでデバッグ大変だったので、ビルドオプション次第で抹消されるようなdebug printを導入した pic.twitter.com/GzUltNpwli
— kamiya (@kamiya_owl) August 31, 2019
ただし確認方法は正しく動くROMのログとCompareすることだったため(8000行ぐらいある)、まずはログ出力を強化した。
— kamiya (@kamiya_owl) August 31, 2019
がんばれかみ子、バグをしばいて立派なまぞくになるんだ。
rust-nes-emulator Issue #25: NESTESTのメニュー画面が正しく表示されるようにする に大体書いてあるが、ひたすらlogを見ていってミスった実装を直しての繰り返しをしていた。
進捗です(だめです pic.twitter.com/gCXKF1StRW
— kamiya (@kamiya_owl) August 31, 2019
だんだん直って
わーい、nestestのメニューが出せるようになった pic.twitter.com/iHdjgGL5SR
— kamiya (@kamiya_owl) September 1, 2019
起動するようになった。(下にCompareしてるスクショが残ってた)
ここで初めて気がついたがunofficial opcodeらしきものがあるらしい。それもすべて実装した。複数の命令の組み合わせか通常存在しないアドレッシングモードか、NOPかのいずれかな気がする。
rustでファミコンエミュ、進捗です(うれしい pic.twitter.com/jMh5d4wZ2M
— kamiya (@kamiya_owl) September 1, 2019
ボタンが押せなくてテストが実行できなかったのでボタン入力を実装。
ここもまた一発で動いてしまった。ここまで死ぬほど動かなかったので、嬉しさのあまりまぞくになるところだった。
指定フレーム数進めて画像比較してボタン押させて~~~~のテスト環境整備した pic.twitter.com/pFnnzPXXBT
— kamiya (@kamiya_owl) September 1, 2019
せっかく動いたのに、後の実装でぶっ壊れると嫌なのでテスト環境を整備した。
nestestしか動かしていなかったので興味本位で動かしたら顔色が悪い。顔色が悪いけど嬉しかった。
顔色が悪いwwwwwwwwwwwwwwwwwwwwwwww pic.twitter.com/BHIMhMEqBg
— kamiya (@kamiya_owl) September 1, 2019
おおおお~~~~~~~~まじか!!!!!!!!!!!!顔色悪いし配管工来てないけど pic.twitter.com/kclWNAxqck
— kamiya (@kamiya_owl) September 1, 2019
ファミコンには実際の表示領域4面分の描画空間(NameTable)が合って、その任意位置を変更できるスクロールレジスタが存在する。
今日の進捗です(めげない心... pic.twitter.com/EP85zkGJ0F
— kamiya (@kamiya_owl) September 4, 2019
実装したらこの有様…。
マリオーーーーーー体がーーーーーーッッッッ!!!!!!!!(rust ファミコンエミュ進捗です pic.twitter.com/ivnfDrIhmE
— kamiya (@kamiya_owl) September 7, 2019
スクロールがない1面だけのゲームに焦点を絞って先にSpriteを実装した。体がばらばらになってしまった…
スクロールもバグってるからマリオが死ぬほど面白い pic.twitter.com/GdHVxQT3V8
— kamiya (@kamiya_owl) September 7, 2019
スクロールのバグと合わせて新しいゲームが完成した。
よっしゃあ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!!!!!!!!!!!!! pic.twitter.com/8HxnyfzHw8
— kamiya (@kamiya_owl) September 7, 2019
Spriteのバラバラ事件は表示反転の論理が逆になっていただけだった。
やったー!rustで書いてるファミコンエミュでスプライトが表示できるようになったー!(
— kamiya (@kamiya_owl) September 7, 2019
ドンキーコングのOPデモが正しく動くようになった!) pic.twitter.com/zB1LjG6uXr
ここまで来るとスクロールのないドンキーはそれっぽいゲームになっていた。
Sprite背景透過できた pic.twitter.com/PBTabChgUT
— kamiya (@kamiya_owl) September 8, 2019
ドンキーコングは、あとはBGの属性テーブルで表示は正しくなりそう pic.twitter.com/AJZYl24caV
— kamiya (@kamiya_owl) September 8, 2019
背景色の判定をきちんと実装したのでそれっぽくなってきた。
もう少し直した。続低テーブルの当て方を間違えていた pic.twitter.com/w44MBt29Qt
— kamiya (@kamiya_owl) September 8, 2019
属性テーブルの参照アドレスをミスっていたので修正したら、顔色はかなり良くなった。
透明色処理が正しく実装されていないと、タイトル表示がこうなるのは正しい。
rustファミコンエミュ進捗です(けっこううれしい pic.twitter.com/ySaYyMJD38
— kamiya (@kamiya_owl) September 8, 2019
ドンキーコングはほぼ完動。
ここまで来たら自分の理解度が深まったこともあり、画面出力と実装をにらめっこしてどこがおかしいか順に潰していった。結構エスパーっぽいデバッグだった気もする。
これが本当のSuper Mario Bros.です(Pad入力は正常に入るようになった。あとはめげない心 pic.twitter.com/QSd1uDbqMt
— kamiya (@kamiya_owl) September 8, 2019
風を感じていた。これはスクロールの値が8倍に適用されている。
rustファミコンエミュ進捗です(もう3声くらいで遊べそう? pic.twitter.com/B3wKd5s2dJ
— kamiya (@kamiya_owl) September 8, 2019
これは属性テーブルがスクロールに追従していないので、背景と色がずれている。
やったー!スクロールがちゃんと動くようになった!!!前のやつは8px単位でスクロールしててかくかくしてる pic.twitter.com/ztvnCvqurJ
— kamiya (@kamiya_owl) September 9, 2019
背景色処理をしていないので、隠しブロックが見えている。あとスクロールがカクカクしている。これはスクロール値の計算間違い。
動画の最後で変死しているが、これはパックンフラワーが見えていない。
Sprite強制全面表示。完全に理解した pic.twitter.com/64SDIaechX
— kamiya (@kamiya_owl) September 9, 2019
これはわかりやすい。
直した pic.twitter.com/Cy8L8kxVe4
— kamiya (@kamiya_owl) September 9, 2019
かなり現物に近づいた。あとは黒い部分だけ。これは[背景色, 背景Sprite, BG, 全面Sprite]とあって 透明色の黒色が選ばれていたら後ろの色(e.g.空の色)を出してあげるのが正解。
やったー!!!!!!!!ウオオオオオーーーーーーーーーーー!!!!!!!!!!!!!!!!! pic.twitter.com/RUaa0l0t9R
— kamiya (@kamiya_owl) September 9, 2019
rustファミコンエミュ進捗です(すごくうれしいhttps://t.co/VvBct6k8EX pic.twitter.com/fdJ1xN1JUL
— kamiya (@kamiya_owl) September 9, 2019
死ぬほど嬉しかった。
いかがでしたか? とりあえずマリオが遊べるまでのツイートをまとめた。最近WebAssemblyに吐いてブラウザでも遊べるようにしたので興味あれば遊んでみていただけると嬉しい限り。
WebAssembly版のポーティングするjavascriptを少し書いていただけでバグまみれになったので、私はRustがすごい良い言語だと感じる。以下紹介する
異なる型の演算はそもそもコンパイルエラー、Overflow/Underflowは実装者が明示しないと実行時エラー。 なんかおかしい→調べたらOverflowだった。みたいなのがなかった。(すぐに場所がわかった)
別の場所からフラグを書き換えるような行儀の悪いことはそもそもコンパイルエラー。
wasmにしてなんか挙動がおかしかったので、console.logデバグしていたがROMを読み込む前にエミュを開始していたっぽい pic.twitter.com/w91y01SxEG
— kamiya (@kamiya_owl) September 16, 2019
ありがとうRust…。私はか弱い凡才なのでコンパイラに守られたい。
これがwasmの本気ッッッーーー!!!!(計算ミスっだだけです。しかもまだ余裕ある pic.twitter.com/DsjzwXuxVT
— kamiya (@kamiya_owl) September 16, 2019
流石にここまで早くなると思ってなかった。3.6msか…。
「この件、意外と反響がある。もともとやるつもりではいたがまともに考えないと。」の顔 pic.twitter.com/TwAnSKHAbr
— kamiya (@kamiya_owl) September 16, 2019
2020/02/02 追記
ちゃんと本に書きました、当日ご購入いただいた方/DL販売でご購入いただいた方ありがとうございました。
以下に購入リンクについてまとめてあります、もしよろしければ。