Help us understand the problem. What is going on with this article?

過去の難案件 PS2のカーネル開発

はじめに

時はPlayStation2も そろそろ終わり。
PS2互換機がゲームセンター等で使われていた時代の事です
私は当時 超新人だったんだけどね
ただ私は 大学を3ヶ月で光速中退して すぐにフリーランスになった変な経歴持ちです
そんな時 ある人物が 掲示板に メモリマネージャやDMA、3DCGについて質問をしていた
ちょうどその時期 仕事が楽だったので 光速で回答しました。

メモリマネージャ作りたいっていうので、簡単な方法として、連結リストでAllocateしていくと簡単だよ
DMAについては 方向のふらぐがこーであーするだけだよ
3DCGについては DirectXを知識は入れてたので なんとなく回答
すると やり取りから1週間で

仕事してもらえますか? PS2の自社タイトルを作ってます。コアエンジニアが不足しています

とメールアドレス宛にメッセージがきたので 請ける事に

これがすべての悲劇の始まりであった

当時のゲーム機

いまでこそ PS4、XBox、NintendoSwitch と、我々が使っているPCやスマホと同じパーツ、アーキテクチャでできているものの
当時のゲーム機は、ゲームに特化した 独自CPU、独自GPU、独自ハードをメーカーが作っていた

PS2に使われていたのは 東芝のEmotionEngine 通称EEである
このEEが 後にも先にもない変態CPUであり、当時は使い方さえあっていれば 恐ろしいパワーのあるCPUだった
(余談だが 後のPS3に使われたCPU Cellも変態である)
ただし 使い方が難しく、後期タイトルになるまで 能力を正しく使ったゲームはあまり出なかった

とにかく PS2は変態ハード。これだけ覚えておけばいい

PS2のスペック

PS2ハードウェア図は下記になる
http://glampert.com/2015/03-23/ps2-homebrew-hardware-and-ps2dev-sdk/
なんのこっちゃ?と思うかもしれないが

EECoreというのが通常のCPUのコアである。
ぶっちゃけ 結構すごいCPUだ
特に驚くのは 128ビット幅のDataBusを採用するのは世界初
https://ja.wikipedia.org/wiki/Emotion_Engine

DataBusが通常のCPUバスで、GS(GPU)やRDRAM(メモリ)とデータのやり取りを行なう
CC上にScratchPadがあるが、昔のハードではよくあった。キャッシュメモリと考え方は近いが
キャッシュと違い、自分ですべて制御する必要がある。
DataBusに比べ圧倒的に速いので、ScratchPadMemoryをうまく利用することが高速化に重要である
GSはいわゆるビデオカードで、頂点変換、ラスタライズ等を担当する

注目はVU0とVU1である。 VUは VectorUnitの意味で、いわゆるSIMD命令を行なう
念の為説明するが SIMDとは、大きなレジスタで複数の計算を同時に行なうアセンブラ命令である
例えば 32ビット浮動小数点同士の掛け算を行なう場合、通常なら 4つの計算を行なうのに4回掛け算が必要なのが
128ビットレジスタを使い 32bitにPackして1命令で同時に4つの計算が行なう事ができる
VU0もVU1も128ビット幅を持っているので 32bit計算なら同時に4つ、16bitなら8つ計算できる
これは 画像や音声など大きなデータを計算するときに非常に有用である

DMAコントローラーがあり、EE Core、VU、GIF、Memory、I/O へ直接データを送れる
一応補足だが DMAとは DirectMemoryAccessの略で、本来はデータを転送する際に 一度CPUを介して
データのやり取りが必要であり、CPUの負荷が増えるのだが
CPUを介さずに 直接メモリの送受信が出来る仕組みのことである

UV0はEE Coreと直接つながっておりコプロセッサーとして機能する
使い方は インラインアセンブラでSIMD命令をかけば VU0で計算される

特に変わってるのはVU1である
VU1はEE Coreと直接つながっておらず DataBusでつながっている。つまりEE Coreとは独立して動く
さらに GIF(GPUのインタフェース)へと Pass2 Pass3で直接つながっている
VU1は MacroModeとMicroModeの2つのモードがあり 切り替えて使うことができる
MacroModeでは VU1は EE Coreのコプロセッサーのような動作が可能
VU0と同じアセンブラを記述し、コードをDataBusを使いVU1に送り、結果をDataBusを使い EE Coreに返す
正直 あまり使わない。
VU1の使いみちは やはり MicroModeである
MicroModeでは EE Coreから命令セットを送り VU1内で処理を行い GIFを通して直接GSにデータを送る
つまり 頂点シェーダーに似たものである

これが PS2が強かった&変態だった&難しかった 一番の原因である

開発環境

DEV KITは 使いやすかった
が 問題は ライブラリである

基本的には 我々のような弱小会社にライブラリは提供されない
提供されるのは
黒い本 と呼ばれる 5冊ぐらいのぶっとい本
中身は それぞれの機能のシステムコールの一覧だ
たとえば DMAの初期化は I/Oポート何番に 数字何を送れ、DMAを EE->VU1にするには I/Oポート何番に何を送れ・・
そんなのが 説明されている

正直 こんなのわかんねー!! って投げ出したくなる

あとは SONYさんのコードサンプルがもらえる
が、サンプルなので ライブラリ化はされていない

ライブラリが欲しい場合には 数百万円で大手メーカーが売ってくれたりする
が 結構高い
ので 自分たちで作ろうということになったらしい

開発スタート

一応 今までPS2のライブラリを作っていたというシニアプログラマがある程度までライブラリを作っていた
ので、まだ全然手がついていないところから 担当することになった

ぶっちゃけ シニアプログラマがお手上げしたところを作るということだ

一番お手上げしたのは VU1プログラムだったので、まずはそれを作ることにした

VU1

VU1は 今でいう頂点シェーダーのようなもので、GSに送る前に頂点データを変形させることができる
(もちろん 他の使い方も出来るが、案件では 頂点シェーダのみとして使っても良さそうだった)
例えば モデルデータは ローカル座標でTポーズで立っているが
これにスキンメッシュを行いWVP行列を掛けて GSに送る
ディフューズ、アンビエント、スペキュラー、テクスチャマッピングをかけてGSに送る
などだ
本当に頂点シェーダーみたいだ

ただ 恐ろしいのは そのシェーダーの書き方である
HLSLやGLSLが当時存在しなかったのは仕方ないが ガチでアセンブラである
しかも 後にも先にも触ったことのない 並列アセンブラ!!
色々と変態だったので 1つずつ紹介する

命令パイプラインとストール

https://ja.wikipedia.org/wiki/%E5%91%BD%E4%BB%A4%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3

https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86

パイプライン自体は RISCでよくある5段パイプラインである
RISCだと命令長が全て同じなのである程度簡単に出来る
一応説明しておくと CPUはメモリから命令を取っただけでは実行できない
例えば上記だと

  • IF (Instruction Fetch) 命令を命令キャッシュから読み出す
  • ID (Instruction Decode/register read) 制御信号を生成し、レジスタ・ファイルをレジスタ指定子で参照する
  • EX (EXecution/address calculation) 数値の計算やロード・ストアのデータやアドレス・分岐先の計算を行う
  • MA (Memory Access) ロード(メモリの読み出し)・ストア(メモリへの書き込み)を行う
  • WB (Write Back) レジスタにデータを書き込む

である
それぞれの命令をパイプライン化することで 実行を高速化している

ところが このパイプラインでは色々なハザードがある
特に多いのが データハザード

例えば レジスタ R1に R2,R3,R4の値を加える場合

ADD R1, R2
ADD R1, R3
ADD R1, R4

と書いてしまうと、R1のレジスタのWBが終わるまで次の命令がストールするので 実際は

ADD R1, R2
NOP
NOP
NOP
ADD R1, R3
NOP
NOP
NOP
ADD R1, R4

と とても無駄になる
ACCレジスタ使う等もあるが R3を破壊してもいいならをテンポラリに使うと
(ニーモニック忘れたので適当)

ADD R1, R2
ADD R3, R4
NOP
NOP
NOP
ADD R1, R3

とすれば いくぶん短くなるが
基本的にはNOPを絶滅するために、他の命令(順番かえても問題ないもの)を移動させ
NOPを減らしていく必要がある

遅延(分岐)スロット

https://ja.wikipedia.org/wiki/%E9%81%85%E5%BB%B6%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88
よく Jumpは遅いというが、それは何故かといえば
遅延スロットの問題があるからだ
パイプラインの話をしたが、今のCPUはパイプラインやスーパースカラ化していて
1つの命令を多段パイプライン処理で高速化している
が、Jumpをした場合は そのときにフェッチしていた命令がすべて無駄になる
なぜなら ジャンプ命令後の命令は 実行してはいけないからだ
そのため ジャンプ命令をデコードでみつけると 命令フェッチを辞め、Jumpしたらパイプラインをクリアする必要がある
それがJumpすると遅くなる大きな理由だ

最近のCPUでは命令予測を行なうため、上記のフェッチミスが少なくなり Jumpしても大きなペナルティは無くなって来ているが
当時のCPUでは ペナルティは大きかった

そのため採用されていたのが 遅延分岐スロットだ!!

先程 デコードしたらJump命令だった場合、フェッチしたデータを破棄すると書いたが
コードの順番をかえて Jumpでフェッチしていた命令を実行すれば ストールは最低限でおさまるはずだ

例でいうと

ADD R1, R2
JUMP xx
Jumpするので実行されないはずのコード

とコードを書くと JUMP命令がデコードされたときに 実行されないはずのコードがパイプラインにフェッチ済だ
ので この命令をクリアしなければならず ストール発生するので
Jumpでフェッチしていた命令は実行するという方針にかえて 上記を入れ替えて

JUMP xx
ADD R1, R2
Jumpするので実行されないはずのコード

とすれば、JUMP命令がデコードされたときには 実行してよい ADD命令がフェッチされている
ここで JUMPして フェッチされたコードを実行して JUMP先の命令をフェッチ
とすれば ストールを回避できる
ので PS2では この方法で 遅延分岐スロットはJUMP後に実行されることになっている

並列アセンブラ

VU1が変態っていったけど、たしかに 各種ストールを自力で解決しなければならない(普通はコンパイラが解決)
それだけで変態っていうのは 可愛そうだが
並列アセンブラは まさに変態だ!!

VU1は SIMD命令とMIPS命令の2つのコアがあり レジスタやメモリを共有した状態で同時に走る
なんのこっちゃ?

ADD R1, R2       jnz R4.x, xxx

と 同じ行に2列で アセンブラ命令をかける
左がSIMD命令で主にベクトルの演算を行い、右はMIPS命令で メモリのロードストア、分岐等を行なう
もちろんレジスタは共有しているので データのストールや 遅延分岐スロットも両方に発生する
また 同じ命令も一部あるため、SIMD命令を MIPS側に追いやることで クロック数を減らす努力も出来る

こうやって工夫すれば 簡単なスキンメッシュで 22クロックぐらいになったりする!

メモリアロケータ

PS2にはライブラリがなく、メモリ管理も独自で作る必要があった
わたしが メモリアロケータは 連結リストが簡単ですよ って教えたのでそのまま実装していた
https://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88
しかし これには大きな問題がある
前方から順番にAllocしていき、使わなくなればフラグをおとし、それ以降全てが未使用であれば削除する
という仕組みだが、途中に開放しないやつがいると メモリが分断される
かといって 当時のゲーム機にGCなんていれる そんな贅沢は出来ない

自分で掲示板で言ったので責任を感じ なおすことに

Linuxのカーネルのコードを参考にし、バディアロケータを作った
https://codezine.jp/article/detail/9325
いちおうLinuxでは BuddyAllocatorの改良版の SlabAllocatorを採用しているが
基本は同じである

BuddyAllocatorも結局メモリ分断される危険性あるが、双方向リストよりはかなりマシだし
なにより 開放がビット演算でとても速いことだ
このアロケータにしてから 体感で30%以上 メモリが有効に使えだした

やっぱ Linuxカーネルソースは 私の教科書だ

DMA

その後手をつけたのは DMAコントローラーだ
これは単に 前任者のバグだと思うが、よくDMAの方向がおかしくて固まっていた
ので、完全に制御した
特に大変なのが 割り込みだ
低レイヤーで書いているので、思わぬときに割り込みが入り、そこでDMAレジスタの値が書き換わったりしていた
ので、適切に 割り込み禁止、許可の制御を行った

3Dライブラリ

オブジェクトを表示する程度の能力しかなかったため、作り直した
といっても 工数に余裕がなかったのでそのゲームに必要なものだけ
当たり判定を最低限作る。スフィア、BOX。
レイキャスト
アニメーション管理
モーフ
色々なシェーダー表現
・・・
技術的には基本的に枯れてるはずなので 物量との単純な戦い

マルチスレッド

PS2自体 マルチスレッド使えたのか覚えてないが
PS2互換機 改良版なので マルチスレッドが使えたので使った
もう 覚えてないぐらい マニアックで苦労した覚えがある
ごめん 何も覚えてないので そっとしておいてください・・

サウンド

データを作ってDSPに送るだけなのに
元のライブラリは 音が永遠にループしたり途切れたりひどかったので
1から実装しなおした
やっぱり割り込み重要!

シニアさんにやってもらったこと

モデルのコンバート
モデルの読み込み
メッシュデータをVU1に送るところ
ゲームに使うライブラリ
キー入力

あれれ?? カーネル私一人で作ってるぞ???

最後に

とりあえず 私がエンジン作ったゲームは無事発売され、海外版も作られ ゲームセンターに可動された
同じエンジンを使って オリジナルも作っていたが エンジン作り終えたのでその初期に私は抜けたが
それらしいゲームは発売されていない

それいこう その会社はゲームから足をあらったようだ。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away