2012年04月20日

うそっ……iPhone のフリップ、遅すぎ……?

 うわーい。iOS と Android で同じゲームが動いたよ!
 ゲームは いつぞやのダルイやつ です。
20120420a.JPG
 ええ分かってますとも。こういうのは動画でお見せするべきですね。いいかげん動画のアップの仕方覚えようと思います。それにしてもスマホの画面を撮んのって難しいな。どう撮っても反射するし。

 移植っても9割方は Android NDK 版と共通ソースだし、どっちも OpenGLES ハードって事もあって、1週間かからず移植できましたね。よかったよかった。
 でも動いたはいいものの、なんでか何もしなくとも処理落ちするような状態だったもんで、その原因をつきとめるのに今日半日かかりましたよ。せっかくだから原因でも書いときます。

 ゲームの画面を更新する場合、普通は描画中の画面をユーザーに見せないように、2枚の画面を用意して交互に切り替える事が多いのですが、iOS の場合はアプリが用意する画面は1枚だけでよくて、その画面に描画後、最終的に presentRenderbuffer というメソッドを使って、 「これ表示してよ!」 と iOS 様にお願いします。
 今回の処理落ちがいったい何の負荷なのか調べたところ、どう考えてもこの presentRenderbuffer だけで 100% とか 200% とか負荷食ってるんですね。
 元々参考にした OpenGL ES テンプレートではそんな事はないようだし、常に遅いならともかく、起動直後は別に遅くなくて、途中でホーム画面に戻ったり画面を回転すると突然遅くなるので、どうも改造しているうちになんか余計な事をしてしまったみたい。

 で、いろいろ削っていく事半日、原因が判明。
 iPhone は縦画面から横画面に切り替わる時、ちゃんと 「くるっ」 とアニメーションしますよね。あの動きはOSが自動的にやってて、90度回転しつつ、縦長から横長に引き伸ばされるのですが、レンダリング済みのゲーム画面を勝手に引き伸ばされても困る訳ですよ。
 そこで、回転するのは構わないからアスペクト比は維持してくれと、ビューのプロパティに以下を追加したんですね。
// 画面サイズは変えず、回転だけするようにする設定
view.contentMode = UIViewContentModeCenter;
// なぜか背景色が白で、ものすごく見た目が悪いので、黒にする
view.backgroundColor = [UIColor blackColor];
 これで比較的マシな見た目にはなりました。が、こいつですよ。この backgroundColor が処理落ちの原因でしたよ。 たぶん OpenGL の画面を描画する前に黒で一度塗りつぶしたりしてるんじゃないでしょうか?。なんで起動直後は処理落ちしないのは全く謎ですが。

 ということで、本日の教訓。
安定動作する前に細かい事とか気にしない!
 ちなみに、今回処理落ちしてた presentRenderbuffer メソッド。ググると予測変換で slow と出るくらい、謎の負荷で悩んでる外人さんが何人もいました。ちゃんと読んでませんが、なんかいろんな要因に引っ張られて遅くなるらしく、この先も悩まされそうだなぁ。

おまけ。負荷測定のしかた

 ついでだから、iOS での負荷測定のしかたも書いておきますか。
 まず、測定したい場所の直前と直後のタイミングで、できる限り正確な時間を取得して、その差分を取れば、その処理にかかった時間は分かりますよね。
 あとは、1フレームの時間が分かっていれば、 (処理時間) / (1フレームの時間) を計算すれば、1フレームの中でどれだけ時間を食ってるかの比率が分かります。これが 100% を超えたら処理落ちですね。
 例えば、CADisplayLink でフレーム管理をしているなら、インターバルが1なら1フレームは 1/60秒なので、その時間で割ればOKです。

 処理時間を測定するためのクラスを作るとしたらこんな感じ?
#import <sys/time.h>
class CProcTime
{
public:
    // 処理開始時間を保持
    void Begin()
    {
        gettimeofday( &begT, null );
    }

    // 処理にかかった時間(秒)を計算
    double End()
    {
        struct timeval    endT;
        gettimeofday( &endT, null );
        double    begSec = begT.tv_sec + begT.tv_usec * 1e-6;
        double    endSec = endT.tv_sec + endT.tv_usec * 1e-6;
        return endSec - begSec;
    }
private:
    struct timeval        begT;
};
// 使い方
CProcTime    procTime;

// 計測開始
procTime.Begin();

// ----------------------------
// この間の処理時間が測定される
// ----------------------------

// 計測終了。Begin からの経過時間を算出
double sec = procTime.End();

// 60fps であれば、1/60 で割れば1フレームに対する比率が分かる
double rate = sec / (1.0/60.0);
 こちらのページを参考にさせていただきました。  ちなみに、Android についても、System.nanoTime(); でナノ秒単位の時間が取れるので、同様の計測ができます。上のサンプルはマイクロ秒なので、ナノ秒だとさらに 1/1000 です。
 ナノ秒ともなると int とか float じゃ全然精度が足らないので注意。

posted by ひこざ at 22:37| Comment(0) | 開発 - iPhone
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。