ページ 1 / 1
FPSが60にならない
Posted: 2009年3月28日(土) 17:11
by ものらす
管理人様のページを参考にFPSを綺麗に60で統一させたいのですが、50~55あたりまでで60には届きません。
デスクトップ画面で右クリックからプロパティを見るとリフレッシュレートはちゃんと60Hzになっています。
当たり判定などの計算が大量に出てくるプレイ中ならともかく、まだほとんど手をつけていない為、特に処理がないであろうタイトル画面などでも50~55あたりに留まってしまいます。
試しに私のPCで龍神録のFPSを見ると、57あたりをウロウロする感じでたまにスペック不足の表示が出てしまいます。
さらにもう一つ試しにということで東方妖々夢も見てみたところ、これは59.8などほぼ60といえる数値でした。
60FPSに合わせる方法はSleepを使うものではなくwhileで一定時間まで時間を稼がせるものを使っています。
while(GetNowCount() - gametime <= 16.66666666 * fps){}//60Hzにあわせる
if(fps > 59){
gametime = GetNowCount();
fps=0;//2行後1に
}
fps++;
16.66666666の部分を25に変えれば40FPSに、20に変えれば50FPSになってくれるので書き方自体は問題ないのではないかと思います。
単なるスペック不足かとも思いましたが、処理がほとんどないタイトル画面でもFPSが低い、東方は問題なく出来ている、という点からして、どうもスペック不足以外の原因がある気がしてしまいます。
もし改善法がわかる方がおられましたら、助言をいただきたいです。
Re:FPSが60にならない
Posted: 2009年3月28日(土) 21:34
by 朽木
while( device is running ) {
auto int NowTime = GetNowCount();
static int NextTime = NowTime + 16;
if ( NextTime > NowTime )
continue;
NextTime += 16;
/* 処理 */
}
の形でもだめですか? ナシ。ループ内に無駄な処理無いですか?
for(;strlen() > value;) {}
とか。
あと、がんばって精度を高めようと
16.66666666 と書いているようですが、
精度は
ミリ秒ですので、16.6でも16と同等です。
Re:FPSが60にならない
Posted: 2009年3月28日(土) 21:40
by SooA
部分的すぎてよく解らないのですが、
while(GetNowCount() - gametime <= 16.66666666 [color=#BBFFBB>* fps[/color]){} //60Hzにあわせる
1000/60 をカウントしているとすると
fps をかけるのはおかしくないですか?
Re:FPSが60にならない
Posted: 2009年3月28日(土) 22:00
by dic
Re:FPSが60にならない
Posted: 2009年3月28日(土) 22:07
by Justy
朽木さん
>精度を高めようと 16.66666666 と書いているようですが
>精度はミリ秒ですので、16.6でも16と同等です
fpsと掛けていて、fpsは最大 60まであがっていきますので、
その時は 60*16.66666666と 60*16とは同等にはならないかと思います。
SooAさん
>fps をかけるのはおかしくないですか
起点となる gametimeが 60フレームに1回しか更新されないので、
一応動くのではないでしょうか。
Re:FPSが60にならない
Posted: 2009年3月28日(土) 22:53
by SooA
> 起点となる gametimeが 60フレームに1回しか更新されないので、
あぁ、なるほどなるほど
つい自分の書き方で考えて思い込みしていたようです ^ ^;
Re:FPSが60にならない
Posted: 2009年3月29日(日) 00:40
by SCI
とりあえず、このままでは原因がつかめないので、試しにこれを実行してみてください。ひどいコードですが、応急処置です。
実行すると、AppLog.txtが出来るので、参考までに結果を教えてください。
私の環境では、大体60FPSになりました。
Re:FPSが60にならない
Posted: 2009年3月29日(日) 10:20
by ものらす
多くの助言ありがとうございます。
朽木さん>
device is runningやfor(;strlen() > value;) {}はC言語以外での書き方でしょうか?
whileのdevice is runningの書き方を試してみたのですが、コンパイルできませんでした。
SCIさん>
部分的に見たことの無い文などもありましたが、とりあえず実行してみると結果は
ScreenFlip = 17 ms
FPS(終値)= 60.16
となり、実行中の数値を見ても大体60に近い数値を出していました。
どこがどうなって改善されたのかまだよくわからないので、そこをご説明いただけたらと思います。
Re:FPSが60にならない
Posted: 2009年3月29日(日) 11:24
by 朽木
>whileのdevice is runningの書き方
分かりにくくてごめんなさい。オレンジ色の部分は「~の意味」のものが入ります。
device is running が分かる関数や変数に書き換えてくださいってことです。
というか、16.6666の時は、何もしなければしないほど60に近くなるんですが、
処理をすればするほど60よりも遅くなるので、61~66(?)程度の誤差は良しとして、
余りの0.6をほかの処理(判定や裏画面)時間に充てた方が良いんじゃないでしょうか@@
「時間進行を完全管理したい」っていうのであれば、フレーム進行ではなく、
時間進行にするという手もあります。
while ( device is running ) {
NowTime = GetNowCount();
EffectSize = NowTime - BeforeTime;
if ( !EffectSize )
continue;
BeforeTime = NowTime;
}
_
Re:FPSが60にならない
Posted: 2009年3月29日(日) 17:36
by SCI
ScreenFlip = 17 ms
FPS(終値)= 60.16
となり、実行中の数値を見ても大体60に近い数値を出していました。
ということは、元のプログラムでFPS算出方法がちょっとおかしいかもしれません。
元のプログラムで描画している画像の量によっては本当に処理落ちしているかも知れませんが・・・
画像の量とかどのくらいですか?
朽木さん
>device is running
まず「デバイスが実行中」の意味を定義しなければなりません。
deviceがDirectXで使うデバイスインターフェイスを指すならば、普通は「使用可能」とでも表現するでしょうが、そもそもDXライブラリではデバイスという概念が隠されています。
Re:FPSが60にならない
Posted: 2009年3月30日(月) 10:07
by ものらす
算出方法ですか…。最初にも書いたように16.6666666のところを25に変えれば正しく40FPSとなり、20に変えればこちらも正しく50FPSになるので、算出方法には問題ないかと思っていたのですが…。
画像の量ですが、タイトル画面の場合たった一つだけです。そして今回の場合画像の量、処理の量が増えるプレイ中でもタイトル画面時とFPSが変わらないというのもあります。
一応表示にかかわる算出方法は以下です。
static int t=0,f[60];
static double ave=0;
f[fps-1] = GetNowCount() - t;//-1はfpsが0ではなく1から始まるため
t = GetNowCount();
if(fps==60){
ave=0;
for(int i=0; i<60; i++)
ave += f;
ave /= 60;
}
if(ave!=0){
DrawFormatString(20,440,GetColor(0,255,0),"%.2fFPS",1000.0/ave);
DrawFormatString(20,460,GetColor(0,255,0),"%.2fms",ave);
}
それと何かの原因でどこかの処理で時間を食っているのではないかと思う現象が一つあります。
以前whileで規定の時間まで時間を稼がせるようにしていると書きましたが↓
while(GetNowCount() - gametime <= 16.66666666 * fps){}//60Hzにあわせる
この{}内にDrawFormatString(200,200,GetColor(255,0,0),"B");と書いてみたのです。
つまり時間稼ぎの為に一度でもループ内に入ったら画面に「B」と表示されるのです。
結果はたまにBと表示されるというものでした。
なので時間稼ぎなどしている余裕はない。むしろ時間が足りていないことを表わしているのではないかと思います。
16.66666666の部分を25(40FPS)などにすれば、やはりBが表示される時間は長くなっていました(それでもたまに消えるほどです…)
Re:FPSが60にならない
Posted: 2009年3月30日(月) 14:34
by SCI
SetWaitVSyncFlag(FALSE);を追加してみてください。
Re:FPSが60にならない
Posted: 2009年3月30日(月) 17:26
by ものらす
SCIさん>
その一行を追加してみると上手くできました。ありがとうございますm(__)m
どんな命令だったのかとDXライブラリのHPに行くと…
ScreenFlip関数の実行時にCRTの垂直同期信号を待つか、を 決めるフラグを設定する関数です。基本的に待ったほうが画面の ちらつきが減り綺麗に表示されます。
この関数は垂直同期信号の意味を理解していて、その上で状態を 変更したい方だけが使用してください。恐らく普通は変更する必要は ないと思います。
…これはどういう意味なのでしょうか…?
綺麗に表示…のくだりを見ると何かを犠牲にしているような書き方ですね…
普通変更の必要はないみたいですが、今回変更したということは何かしら私の環境は普通ではなかったのでしょうか?
Re:FPSが60にならない
Posted: 2009年3月30日(月) 17:49
by 御津凪
垂直同期(VSync で検索すると出てきます)というのは、画面全体の転送(描画)が終わるのを待ち、転送中の色情報が書き換わることによる
チラつきを抑える役割をはたしています。
画面を60Hz(ディスプレイによって違いますが)間隔で垂直同期が行なわれ、転送を行なっているのですが、
この待ち時間がうまくいかないディスプレイが(他にも要因はありますが)あり、
何もしていなくても AS さんのように FPS が 60 に届かなかったり、逆に 60 を超えることがあります。
この垂直同期を切るという事は、画面の転送タイミングを無視して画面の更新を行なうことができ、
上記要因による不具合が解消されることを意味します。
(代わりにチラつきや転送の境目の線が出ます)
ただし、待ち処理を CPU 側で行なうため、 CPU の占有率が飛躍的に上がります(シングルコアの CPU なら100% まで上昇)。
今回の場合、
while(GetNowCount() - gametime <= 16.66666666 * fps){}//60Hzにあわせる
とあるので、望みどおりの時間までここをループし続けているため、 CPU を占有しています。
# Sleep を使えば CPU をさほど占有せずに済みますが、精度が悪いので、更新がカクつきます。
Re:FPSが60にならない
Posted: 2009年3月31日(火) 02:35
by ね~す
東方シリーズでVSyncの利用をユーザー側で決められるのは、
このような事例があるからなんですね。
一つ、勉強になりました。
(もっとも、私は質問者ではありませんが。)
Re:FPSが60にならない
Posted: 2009年3月31日(火) 12:36
by SCI
例えば、75FPSや50FPSで更新しているPCでプログラムを起動すると、移動処理をフレーム当たりの移動量で計算していた場合、移動速度が変わります。
プログラム側でFPSを調整するか、もしくは移動処理を時間で計算するかなどで処理速度を一定にします。
Re:FPSが60にならない
Posted: 2009年3月31日(火) 14:59
by ものらす
なるほど…勉強になりました…
調べてみるとFPSの問題は結構皆さん苦労されてるようですね。
VSyncをオフにすることも一つの手段として皆さんやっておられる印象でした。
チラつきは今のところ気になりません。CPUの方も垂直同期によってかなりの差があることを確認できました(私の場合100%まで上がりました)
しかしこうなるとやはり100%だなんて大丈夫だろうか…?という心配が出てきます。
何か弊害が現れそうな雰囲気ですが、どうなんでしょうか…?
通常、垂直同期を切る必要はないということは私のディスプレイが特殊なタイプであったのかと思いますが、そうなると東方は「垂直同期を取らない」のチェックが外れていた(オンになっている)にもかかわらず、何故60FPSになることができたのかという疑問が新たに出てきてしまいます。
さらに少し調べているときにフルスクリーンとウィンドウでも差が出るというのを見かけたので、今まではウィンドウで作成を進めていましたが、久しぶりにフルスクリーンで起動してみると垂直同期をオフにしなくても60前後で安定をするという、これまた難解な動作をしてきました。
ちなみに東方をウィンドウで起動してみると、こちらはウィンドウであっても60前後で安定してました。
このあたりはどう解釈したらよいのでしょうか?
垂直同期をオフにしてCPU使用率が上がっても問題はない。であるのなら一挙解決ですが、どうもそう上手くはいかない雰囲気を感じ取っています…
Re:FPSが60にならない
Posted: 2009年3月31日(火) 15:23
by SCI
> しかしこうなるとやはり100%だなんて大丈夫だろうか…?という心配が出てきます。
ゲームをしながら別の重要な仕事をする、などを保障外にしてしまえばいいです(笑
ただ、そのままではCPUがオーバワークなので、Sleep(1)を入れるのが一般的です。
> 通常、垂直同期を切る必要はないということは私のディスプレイが特殊なタイプであったのかと思いますが
いや、今回の場合、ScreenFlip()による信号待ちと、while回しによる時間稼ぎを両方行っていたためかと思われます。
ASさんのディスプレイは60Hzです。
東方はフレーム毎の移動量を計算しているので、60Hzで動くことを想定しています。
ここで、ディスプレイが50Hzとかだった場合、素直に信号待ちしていると速度がおかしくなるので、この場合はプログラムの方で時間調整をします。
#追記
「両方行う」は原因ではないですね。
詳細なコードが分からないのでテストできませんが、整数と小数の比較での精度が問題でしょう。
「 < 16.66」と書くと、左辺がdoubleに型変換されるので、実質17ms待ちとなります。
上で出したサンプルコードでは、「 < 16」としているので、理論上60FPS以上となります。
Re:FPSが60にならない
Posted: 2009年4月01日(水) 12:14
by ものらす
保障外(笑)確かにそれもありですね。
Sleep(1)を入れるとCPU使用率が激減しました。
東方は60Hzで動くことを想定している。なのでディスプレイ設定が60Hzであったならwhileによる時間調整などは行わない。なぜなら60Hzの設定になっているので、ほっといても60FPSになるから。
ただし50Hzや75Hzだった場合は垂直同期を切るなどのなんらかの時間調節を行って60Hz環境を作り出す。その代わりティアリングなどが起こるかもしれない。
…ということですよね?
しかし今回の私の場合、whileによる時間稼ぎの行をなくして、垂直同期を切らないように(つまりディスプレイの60Hz設定にあわせる)しても、60FPSにならない(約50~55)のは何故でしょう…?理解が及ばず申し訳ないです。
普通このような設定にすれば、60Hz環境では60FPS、75Hz環境では75FPSになると思うのですが…
もちろんwhileによる時間稼ぎの行をなくして、垂直同期を切るとものすごいスピードでゲームが進行しました(笑)
Re:FPSが60にならない
Posted: 2009年4月01日(水) 13:28
by 御津凪
ウインドウモード時では、画面への描画処理にコピーモード(描画データへコピーする処理)しか行なえないため、
垂直同期を取っていても転送遅延やちらつきが出ます。
(私の昔のノートPCでは40~60を行き来していました)
これは転送タイミングをうまくあわせることで 60 に安定させることが可能ではないかと思います。
フルスクリーンモードの場合、ほぼ完全に画面処理を占拠でき、
且つ描画処理にフリップモード(描画データへのアドレスを入れ替える処理)が行なえるので、
垂直同期を待つだけで容易に指定されたFPSを維持できます。
Re:FPSが60にならない
Posted: 2009年4月01日(水) 14:37
by SCI
>しかし今回の私の場合、whileによる時間稼ぎの行をなくして、垂直同期を切らないように(つまりディスプレイの60Hz設定にあわせる)
>しても、60FPSにならない(約50~55)のは何故でしょう…?理解が及ばず申し訳ないです。
この場合、Sleep(1)は入れてないですよね?
whileを入れた場合の原因は型変換ですが、ScreenFlip()だけの場合でも60FPSにならないなら、原因はちょっとわかりません。
スペック不足でもないし、計算方法も間違ってはいないようなので・・・
Re:FPSが60にならない
Posted: 2009年4月01日(水) 14:48
by SCI
またASさんの場合、過去に
ScreenFlip = 17 ms
という結果が出ているので、少なくとも58.82FPS以上は出るはずです。
Re:FPSが60にならない
Posted: 2009年4月02日(木) 10:35
by ものらす
お二方ありがとうございます
SCIさん>
Sleep(1)は入れておりません。完全に待ち時間ゼロの状態です。なのに…うーん何なんでしょうか…
過去に17msという数値を出しましたが、あれは垂直同期をオフにした時の結果です。
垂直同期をオンにするとやはり50~55あたりになってしまいます。
垂直同期をオフにすれば60FPSにはできますが、ティアリングが起こっているのを確認できてしまったため、垂直同期をオンにしたまま60FPSを確保したいのです。
御津凪さん>
>私の昔のノートPCでは40~60を行き来していました
私の現状と似た感じかもしれませんね。ウィンドウ時のみFPSの低下が起こります。
転送タイミングを合わせるとはどういうことでしょう?
Re:FPSが60にならない
Posted: 2009年4月02日(木) 11:55
by 御津凪
> 転送タイミングを合わせるとはどういうことでしょう?
ウインドウモードでは垂直同期自体がずれているのが原因でFPSにズレが出ているはずなので、
FPSのズレに応じて移動処理関数の呼び出しをコントロールする、というものです。
これにより、描画の間隔は変わりませんが、実際のゲーム速度は変わらないようにすることが出来ます。
これはいわゆるフレームスキップというもので、描画に時間がかかったらフレーム時間を合わせるために
移動処理関数を複数回呼び出すことで転送タイミングとあわせます。
ただ、シューティング・アクション系などの常に動くゲームに対してフレームスキップ処理をすると、
処理が重くなった時にゲームにならなくなります。
(描画されていない間も移動処理していることになるため)
一般のPCゲームでもフルスクリーンとウインドウでの描画更新自体に差があるのがほとんどです。
ですので、いっその事フルスクリーン推薦としておくのも一つの手かなと思います。
ちなみに、東方地霊殿でもウインドウモード・60FPSに合わせた設定でティアリングが起こりました。
Re:FPSが60にならない
Posted: 2009年4月02日(木) 22:41
by SCI
>過去に17msという数値を出しましたが、あれは垂直同期をオフにした時の結果です。
私の提示したプログラムのことを言っているので、これは違います。あれはオフにする前の結果を取っています。
>ティアリングが起こっているのを確認できてしまったため
ダブルバッファリングをしていますか?
Re:FPSが60にならない
Posted: 2009年4月03日(金) 10:58
by ものらす
SCIさん>
書き込みナンバー(?)31336のときに提示していただいたソースですよね?
確かに初めの一回は垂直同期オンの状態で計測していますが、やはり一度だけなので早いときもあれば遅いときもある、といった感じではないでしょうか?
試しに一度目を計測しファイル出力する関数を少し改変し、一度だけではなく180回の平均を出すようにしてみたところ
ScreenFlip = 21.166666 msや20.1 ms、最速でも18.727778 ms
となりました。
5回ほど実行した中で最速記録をもってしても約53FPSです。
改変したものは以下です。
int t3=0;
for(int i=0; i<180; i++){
ScreenFlip();
int t1 = timeGetTime();
ScreenFlip();
int t2 = timeGetTime();
t3 += t2 - t1;
}
double a=t3/180.0;
FILE *fp = fopen("AppLog.txt", "w");
fprintf(fp, "ScreenFlip = %f ms\n", a);
fclose(fp);
あとダブルバッファリングですがSetDrawScreen(DX_SCREEN_BACK)を書き加えた後、ScreenFlipで裏画面と表画面を入れ替えて表示させることですよね?それならば問題なく出来てると思います。
御津凪さん>
なるほど。その方法でしたか。実はかなり前になりますが、作成当初はその方法を用いていました(笑)
ですが、それよりは現在の方法のほうがよいのでは?と考え変えた次第です(まぁその方法で今引っかかってるわけですが…)
フルスクリーン推奨ですか…確かにもうそれぐらいしかないかもしれないですね…
ただフルスクリーンもフルスクリーンでディスプレイによって不具合が起きたりするとどこかで見かけたことがあるので、今後また引っかかって、この掲示板でお世話になることがあるかもしれません。
かの東方でもウィンドウモードだとティアリングが起きるということで、この問題はかなり難しく皆さんそれぞれある程度妥協をしているのかもしれませんね…
Re:FPSが60にならない
Posted: 2009年4月03日(金) 12:02
by SCI
>確かに初めの一回は垂直同期オンの状態で計測していますが、
ですよね。この部分について言及したつもりですが、分かりにくかったようで。
>あとダブルバッファリングですが・・・
いよいよスペック不足な気がしてきました。
一度もスペックを明示していないので「十分なスペック」と勝手に仮定していましたが、どうも早計だったようです。
とりあえず、ダブルバッファリングしていればティアリングはある程度抑えられるはずですが、それは私のPCだけかも知れませんね。
妖々夢でウィンドウ・同期無しでもティアリングは発生しないので。
Re:FPSが60にならない
Posted: 2009年4月04日(土) 15:11
by ものらす
スペック不足ですか。
それならそれで原因もはっきりして、ある意味解決かなとも思いますが…
妖々夢はウィンドウ、同期なしですと、少しだけティアリングを起こします。
しかし、私のプログラムと比べるとティアリングの起こる頻度は私のほうが上なようにも思えました。この比較が正しいかはちょっと自信がないですが、そんな気がするという感じです。
あと妖々夢はウィンドウ、同期ありでもやってみたところ、60FPSを保てていました…
スペックは確かにあまり高いとは言えませんが、逆に低すぎるものでもないのではないかと思っております。
Celeron CPU1.6GHz
メモリ1GB
で、OSはXPです。