#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」以外は全て
自動生成ファイルですよ。お魚さんの方を別館から落としてもらえば、意味が判ると思います。