#7DGEN再び(2006-02/07) | //www.旧型、PSP開発幼稚園.game.jp/(本館)

#7DGEN再び(2006-02/07)

#7DGEN再び、お魚さんの為にも必要です。(2006-02/07)

(注:このDGENは1.00です。最新のDGENはもっとずっと進んでいます。 スキルがあり、開発をしたい方は開発者に直接コンタクトをとって下さい。 私は開発者とは全く関係のない第三者です。 あくまでこのページの話は実験用途(教育用)です。)
■DGEN100を忘れた訳ではありません。 パフォーマンスだけ見れば、 DGEN100 > 管理人版DGEN100 >>> 管理人版お魚 です。 単純にコンパイルしたDGEN100に負けてます。 ましてや DGEN120 >> DGEN100 ですから。
遅いエミュ等、速いエミュがあれば無価値。
だからといって本当に価値がないのではありません。
■さて、DGEN100。 PNGも読めないし、ZIPも読めない。 これは、ライブラリが形だけリンクされていて、 コンパイルも通るし、実行もできる。でも機能しないって状態。(別館のRINも全く同じ状態) 恐らくこれらのライブラリを使う為には、ライブラリのソースを弄る必要があるのではないのかと思います。 (ただコンパイルしただけでは駄目) 原因は未だに判りませんが、どうせ使えないライブラリなら、使うのやめちゃえ。 って事で。外してコンパイル。エラーが出たんで、さして(管理人には)関係ない所はコメントアウト。 ステートセーブとか改造コードとかZIP-ROMのロードとかその辺だったと思う。 なあんだ。ライブラリ1つもいらないじゃん。(あとでRUKA版のUNZIPLIB.Aをリンクしよう) それから気がついたのですが、どうも'-lc'はあやしい。これがあると、コンパイルは通るものの、 PSPに持ってって、実行できなくなる場合がある。(起動に失敗しました) RINのMakeは直したのに、DGENのMakeはそのままだった。 もちろんうっかり、"startup.S"を"startup.s"にリネームしたので、消されてしまった。 ("*.s"のファイルは何処にあっても、コンパイル中断で、問答無用に削除される) という事もあるのですが。 単純にMakeを書き換えると、'-lc'のせいで、実行できなくなります。 これは、ソースの位置関係が移動して、単純にパスが長くなっただけの状態でこうなります。 ライブラリその他ソースのインクルードはすべてうまくいっていての話です。 (でないとコンパイル通ったとは言わない) やってみないと、「意味」わからんだろうな。(表面的な意味ではない)
■さて、DGEN100。(記憶喪失になった訳ではない) とにかく弄れば弄るほど、パフォーマンスダウンしてます。 それは、どうでもいいちゃあどうでもいい。(現在の状況はとてもそんなレベルではない) んですが、やはり気になったので。 CPP関係は一つのオブジェクトに纏めました。(リンカにリンクさせない) 具体的には、訳判らなくなるので専用のフォルダに集め、(必要な作業ではないがこういう事が大事) 「mdcore_main.cpp」のみとし、あとの物はすべて「mdcore_main.cpp」内でインクルードです。 これで、リンカが噛まなくなります。 要するに、
//////////////////////////////////////////////// /// mdcore_main.c //////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> //////////////////////////////////////////////// #include "./md.h" //////////////////////////////////////////////// #include "./../emu_config.h" #include "./../emu_state.h" #include "./../psp_pad.h" //////////////////////////////////////////////// #define _USE_FM_CORE ///////////////////////////////////////////////// static int sound_is_okay = 0; static md mdcore ; #include"myfm.cpp" #include"mem.cpp" #include"md.cpp" #include"vdp.cpp" #include"ras.cpp" #include"mdfr.cpp" #include"graph.cpp" #include"save.cpp" #include"md-joe.cpp" ///////////////////////////////////////////////// #ifdef __cplusplus extern "C" { #endif 略
って感じです。 <お昼ご飯でし、つづくよぉ。12:23> 部分的にクラスを破壊してみた所があるんですが、 おそらくここがパフォーマンスダウンしてる原因かな。と思い。 (素直にクラスに入れりゃぁいいんですが) とりあえず、一つのOBJになった事だし、 改造の都合で取り敢えずGLOBAL変数にしてたところを、 STATIC変数に変更してみました。 (それなら、C的には大丈夫な筈でしょ?、CPPのクラスのprivateみたいなもんです。) (注:C言語初心者様、この場合のSTATICは関数内のSTATICとは全然関係がないよ。 意味まったく違うんだよ。予約語だけ一緒なんだよ。C言語判る人なら常識だけどさ) 但し、 グローバル < スタティック < クラス(public) < クラス(private) かもしれなくて、やっぱ駄目かもしんないです。 (管理人C++のその辺はよく知らない) で、結局「BF03チェック」の結果は代り映えしないです。 いずれにせよ。パフォーマンスに関しては、 明らかに不要なルーチン(使ってないのにリンクされてる)を削除すると、 何故かパフォーマンスが下がるので、何がボトルネックになっているのか、未だに良く判りません。 (alignだったりして。でもそれなら、最適化オプションの-O3でなんとかなる筈)
■お魚さんへの(音関係)移行について、 始めから計画してる事なのですが、音関係は一つに纏めてしまおうというのがあります。 というのはMDの構造上一箇所でレンダリングしても差し支えないからです。 (つまりUPDATEONEは1つしかなくその中でDACとSNとFMのレンダリングを行う) YMの入力クロックは68000と同じですし。SNの入力クロックはZ80と同じです。 ここはDGENが間違ってる(っていうか実装の都合によりわざと間違えている。多分) ところです。(単純に間違えてるだけって事が判明しました。2006-02/08、01:28、追記) DACはキャッシュしないと駄目ですね。(←この表現では意味不明かも) このVerのFM.Cではなくて、もっと新しいVerのFM.Cから該当部分を抜いてくる必要があります。 (さとうたつゆき様、VERのDACを改造して使うのなら) DACは現在のDGEN100の実装では、外部で実装し、画像レンダー(ラスター)とも連動してます。 さとうたつゆき様のこのVerのFM.CはDACがバグバグなので、使ってません。 YMとSNのクロックが整数倍で同期できるのですから、無理に別々にレンダーして後でMIXingしなくても、 初めから同時にレンダーすれば良いのです。 具体的には、音のレンダーがかかる可能性は3ヶ所で、 1.68000/Z80共通ルーチン:SNのレジスタが更新された場合。 2.68000/Z80共通ルーチン:YMのレジスタが更新された場合。 3.フレームの終わりまで達した場合。 これらの場合にそれまで部分をレンダリングすれば、精度的には一番良い方法です。 さらに、メガドラの場合は、 4.ラスターの終わりまで達した場合。 を入れないとまずいかも知れません。これはDACの都合です。4を入れるか入れないかで、 実装の都合が大きく変わります。しかし、管理人の見解では、(2.3.)をきちんと実装すれば、 4は取っても良い。と思います。(DGENは4.を実装する為にDACルーチンが別になっている)
上記は理想論です。管理人が5年前に作成した、PCのエミュでは全て実装し、動作を確認しました。 (だからDACテーブルなんてもんがあるんだよ) が、PSPのエミュでは基本的な動作速度が全然違うので、 (PCでいったらPen200クラス、0.2GHz程度) 何を入れて何を入れないか、選択に悩む所です。 いっその事、2.3.を取っ払って、4.を入れれば、殆ど影響がない気がします。 そおいえば、PCのエミュでもそうした気がする。(いまソースが行方不明中) それなら、ほぼ問題出ないよなあ。そおしよ。 って事で、4のみUPdateOneが呼ばれる。(場合によっては3.も) つまり、一箇所ですんじまいますね。 そおしよ。そおしよ。いい案浮かんで良かった。 これなら、多分うまくいくと思います。
ちなみに現状のDGEN100は、 A.YMのレジスタが更新された場合。FMのUPDATEONE(DACはフックして渡さない) B.SNのレジスタが更新された場合。SNのUPDATEONE C.DACはフックして別ルーチン。ラスターの具合を見てDACのみレンダー(う~んちょっち正確な表現ではないな) D.フレームの終わりまで達した場合。UPDATEONE。 E.どこかで(よくしらん)MIX→音だし となってます。 D.の実装より、前項4.の実装のほうが厳しそうですが、44100Hzで音だしする場合。 1フレームあたり(1秒間60フレームだから) 44100/60で、735回です。 メガドラのNTSCの場合1フレーム==262ラスタですから、 1ラスタあたり2.8回 前項4.の実装ならば、 UPDATEONEのlengthループは、高々3回しか回らないんですよ。(もっと回ると思ったでしょ?)
サブルーチンコールのオーバーヘッドを考えた場合。 仮にDACもレジスタもそのフレームはまったく更新されなかったとして、 フレームの終わりに更新の場合は1回コール、length=735ですんじまうのに対して、 ラスターの終わりに更新の場合は262回コール、length最大3ですね。 こんなもったいない事は、PCなら屁でもないですが、PSPでする訳がない。 ここはYM(DAC含む)とSNのレジスタが書き換わったかどうかフラグを立てれば済む話です。念のため。 ですから、フラグに変化がなければ、ラスターの終わりでUPDATEONEを呼びません。 この方式でも「いつでもラスターの終わりに更新する。という訳ではありません。」念のため。 初めの方に書いたレジスタキャッシュってのは、そおいう意味です。(2006-14:00ぐらい)
■PSPのメガドラエミュで、FMが重い→UPDATEONEが重い。ってのは、事実ですが、 UPDATEONEは44100Hzの場合1フレームに735回しか回らないので、 「一回一回が、非常に重い。」 という事です。 だから、DGEN100の初めの状態で、 44100用UPDATEONE 22050用UPDATEONE 11025用UPDATEONE と三つに別れていたのを、一個に纏めても、パフォーマンスさして変わらなかったでしょ? (詳しくは2006-01/26の日記参照) それは理屈上殆ど変わらない筈だからです。そこはボトルネックではありませんからね。 (2006-14:10)
■inline についての苦言。 みんな、C言語のdefineについて知らないみたいだから、特別レクチャー。 「inlineとdefine。最適化MAXまでかけときゃ、同じじゃないの?違うの?」 違います。運が良ければ、同じになる場合もありますが、ちょっとした事で最適化失敗して違います。 それにGCCは最適化そのレベルまで保証してる訳じゃぁないんだよ。 例:DGEN100の「PSP_SCREEN.C」
inlineの場合。
inline void PSP_Screen_WaitVsync ( void ) { sceDisplayWaitVblankStart() ; } inline void PSP_Screen_WaitVsyncFast ( void ) { sceDisplayWaitVblankStart() ; } void PSP_Screen_SetBkColor( int color ) { psp_screen_bk_color = color ; }
defineの場合(defineが効くように、位置を変える必要がある。呼ばれる前にdefineされてないと。)
#define PSP_Screen_WaitVsync ( aaa ) ¥ { ¥ sceDisplayWaitVblankStart(); ¥ } #define PSP_Screen_WaitVsyncFast ( aaa ) ¥ { ¥ sceDisplayWaitVblankStart() ; ¥ } #define PSP_Screen_SetBkColor( color ) ¥ { ¥ psp_screen_bk_color = color ; ¥ }
一見、どちらも同じ様にみえます。が、違います。特に3番目はどんなに最適化しても、 余計なコードが残るでしょう。colorが本当にintかどうかの型チェックが入りますから、 それは注意してみれば判りますが、プログラマーが型チェックしろと明示してあるので、 外す訳にはいかないからです。 1.2.はvoid,voidだから、同じなんじゃあないの?。同じになるのが理想なんですが、 現在のGCCの最適化技術では、同じになったり、オーバーヘッドが残ったりという状態です。 これは最適化の実装方式がパスの下流になっている為に、起こる現象です。 GCCは何もR4000の専用のコンパイラ(アッセンブラ)ではありません。 (たとえR4000用にコンパイラをビルトして作成しても。) 平たくいえば、どのCPUにも対応できるよう、最適化はあとでまとめてやる。という方針になってます。 まとめてやる最適化の方針としては、まずそうな所(重いと思われる)から先にやるので、 うまく取れない場合があります。人間がみれば、こことここ全部取ればおしまいじゃんって、 見て直感で直ぐに、判りますが。コンパイラがこなす最適化は「関数のオーバーヘッドを取る」事だけでは、 ありません。「ほかの変数との共通部分をとる」とかいろいろ最適化の手法はあるので、 他の手法の最適化強度(優先順位)が高かった場合には、結局取りきれなくて結果的にゴミが残ります。 よしんばCなら、なんとかなっても、CPPは中身はかなり泥臭いですから、どうにもなりませんね。 (外見(そとみ)と中身は違うんだよ。クラスの世話や関数の世話。関数や演算子のオーバーロード等、 やることは幾らでもある。例えばmd::foo()って関数は、内部でCに変換され、 md->aaa->bbb->ccc->ddd->foo()みたいな感じになって、Cコンパイラに渡される。それをR4000の アッセンブラ(この時点が噂の小文字の[*.s]ファイル)に変換し、最適化が入る。それをアッセンブルして OBJを生成し、リンカに渡され、リンカの最適化機能が入り、やっとA.OUTができる。あとは知ってるよね。)
注意点: 1.単純に上記の様に、「define化」すると、C言語の持つ「型変換機能」が外れる事に注意してください。 2.C言語のスコープ機能にも、注意する必要があります。スコープが効いたらまずい場合は、例えば、 さらに下記の様に直します。(違うんだよ) 「同じでない」と知識で知っていても、具体的に何処が「同じでない」のか、解説してる所。 あまりみかけない、ですからね。(つまりは、みんな判ってないんだろな)
#define PSP_Screen_WaitVsync ( aaa ) ¥ sceDisplayWaitVblankStart(); #define PSP_Screen_WaitVsyncFast ( aaa ) ¥ sceDisplayWaitVblankStart() ; #define PSP_Screen_SetBkColor( color ) ¥ psp_screen_bk_color = color ;
普通は、(上と全く同じ記述)
#define PSP_Screen_WaitVsync ( aaa ) sceDisplayWaitVblankStart(); #define PSP_Screen_WaitVsyncFast ( aaa ) sceDisplayWaitVblankStart() ; #define PSP_Screen_SetBkColor( color ) psp_screen_bk_color = color ;
ちなみに、 inline void PSP_Screen_SetBkFgColor( int bgcolor,int fg_color ) { psp_screen_bk_color = bg_color ; psp_screen_fg_color = fg_color ; } みたいな関数があった場合。(説明用に作ったんで本物にはないけど)define化は、 #define PSP_Screen_SetBkFgColor( bgcolor, fg_color ) ¥ { ¥ psp_screen_bk_color = bg_color ; ¥ psp_screen_fg_color = fg_color ; ¥ } スコープが効いたらまずい場合は、(普通は簡略化してこっち) #define PSP_Screen_SetBkFgColor( bgcolor, fg_color ) ¥ psp_screen_bk_color = bg_color , ¥ psp_screen_fg_color = fg_color ;
良く注意して見てくれ’;’と’,’が違うだろ。 これは、C言語の’,’機能で、スコープを使わない時に使う機能だ。 もうひとつdefine化の注意点として、if( 式 )やfor(式;式;式)やwhile(式)に入った場合だ。 if()やwhile()はGCCの場合、コンパイルエラーで気が付くが、 forの場合は、変に辻褄があって、取れないバグになる可能性がある。 上記の場合は、丸括弧でくくったり、最後の”;”を取ったり、いろいろ用途によってあるんだけど、 どっちが判りやすいかは好み。まあ全部最後の”;”は取った方が現実的だな。 例えば、こんな感じ。 #define PSP_Screen_WaitVsync ( aaa ) sceDisplayWaitVblankStart() #define PSP_Screen_WaitVsyncFast ( aaa ) sceDisplayWaitVblankStart() #define PSP_Screen_SetBkColor( color ) psp_screen_bk_color = color #define PSP_Screen_SetBkFgColor( bgcolor, fg_color ) ¥ psp_screen_bk_color = bg_color , ¥ psp_screen_fg_color = fg_color 最後に”;”がないのはバグではない。
■別館に現在の管理人版DGEN100をあげときました。既にうちのはもちっと進化してますが(21:13分) 殆ど同じものです。それで、FSKIP0、VSYNCなし、サウンド11025Hz、最大60fps、Ge全画面で。 バーニングフォース一面のボス(こいつは奇数フレームと偶数フレームで交互にちらつくので判りやすい) (単に、FSKIP0でフレームのチェックをしたいだけなら、ローリングサンダー2のキャラクタ選択画面がお勧め) で最悪42fps程度。(ちらつきの画面のみ)でした。 これは平たく言えば「1秒間に42枚画面を書き換えています。」という事です。(1フレームあたりの動作時間は) つまり1000割る42で、約23.8[mSec]で動作しています。本物は60枚書き換えますから、 1000割る60で、約16.6[mSec]で動作しています。(この数字、液晶ディスプレイのカタログみたいやな) 238割る166は約1.43ですから、このちらついている状態では、 本物の1.5倍程度とろい。という事になります。 つまり、1/3程度の処理時間を削減できれば、結構速く動く事になります。 現状のソースを眺めれば判りますが、これは不可能ではない数字ですね。 アセンブラなど一ヶ所もないし。 主にCPPのクラスの中とUPDATEONEの中。それにCPUの中を弄る必要がありますが、 決して不可能ではないです。例えば正規版のDGENは、その後画像のバッファを実装したそうで、 それがどの程度、効果があるのかどうかは、やってみなければなんとも言えないですけど、 現実の正規版はこれよりずっと速いですし。 ちなみにZ80は手を付けてないどころか、まだソース見てもないです。 68000コアの割り算も取ったりしてないですし。(基本的になにも手を付けてないです。 つまり、MUSASI3.3(古版)に入れ替えてみたりして遊んでただけ。) (DIV系命令でもないのに、割り算を使ってる所で、すごく簡単に取れる所がある。 例えば引数が、バイトでくる事が確定していて割る数が大きいのなら、 最悪値を考えて回数引けば済む。 例えば256割る27は最悪でも9回27を引けば良い) この辺本当にやりたいのなら、過去日記を読んでね。「m68make.c」と「m68k_in.c」以外は全て 自動生成ファイルですよ。お魚さんの方を別館から落としてもらえば、意味が判ると思います。