AndroidのSurfaceViewでのmatrix処理が非常に重い

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
板野

AndroidのSurfaceViewでのmatrix処理が非常に重い

#1

投稿記事 by 板野 » 10年前

Androidでゲーム開発していてフレームレートの低下について困ったことがあったので質問させていただきます。

スプライトアニメーションでキャラクターをアニメーションさせているため、Bitmapの数が多く1つのキャラクターに10つほどのBitmapを使用しています。
このキャラクターが1画面中に30体ほどでるゲームを作りたいのですが、非常に処理が重く10体ほど登場させただけでフレームレートが下がってしまいます。

初めて制作するAndroidゲームなので軽量化についてほとんど経験がないです。
どうしたら多くのbitmapを高速に描画できるでしょうか?


現在のソースコード、描画処理の一部を載せます。

コード:

    //メインループ部分とマルチスレッドの描画処理(参考:dixq.net/Android/s02_01.html)

    @Override
    public void run() {
            while (thread!=null) {
            	
            	//前フレームのタッチ情報をコピー
           		notifyTouch.Mimicry(touch);
           		gameManager.SetTouch(notifyTouch);
            	
            	//次の画面を取得
            	RunScrAdmin = RunScrAdmin.GetNextScreen(gameManager);
            	RunScrAdmin.Update(gameManager);
            	Draw(getHolder());
            	
            }
    }
    
    private void Draw(SurfaceHolder holder) {
            Canvas c = holder.lockCanvas();
            if(c == null)return;
            c.drawColor(0x00000000, Mode.CLEAR);
            
            RunScrAdmin.Draw(c,gameManager);
            RunScrAdmin.ScreenChange(c,gameManager);
            
            //c.drawRect(0, 0, 800, 480, apaint);
            
            holder.unlockCanvasAndPost(c);
    }

コード:

	//SurfaceViewのDrawスレッドから呼ばれるメソッドで、フレームレートの低下の原因と思われる描画処理部分
	public void Draw(Canvas canvas,GameManager gameManager,Object2D object2D){
		
		Matrix matrix = new Matrix();//変数共有して軽くできるかも?
		MotionNode motionNode;
		Bitmap image;
		
		if(motionList.getMotion(motion) != null){
			//モーションの種類(歩くとかジャンプ).モーションのフレーム(連番).モーションのノード(1つの画像の座標とか角度)
			//つまり現在のモーションの現在の時間に使うすべてのモーションのノードをループで描画します。
			for(int i=0;i<motionList.getMotion(motion).getMotionFrame(animationTimeLine).getMotionNodeSize();i++){
				//ノードは、描画順にソートされています
				//つまりループ内で描画してもおk
				
				//共有変数の設定
				matrix.reset();
				motionNode = motionList.getMotion(motion).getMotionFrame(animationTimeLine).getMotionNode(i);
				image = gameManager.getBitmap(motionList.getImageID(motionNode.getRes_id()));
				
				matrix.postScale(motionNode.getScale_x(),motionNode.getScale_y());
				matrix.postRotate(motionNode.getAngle(),image.getWidth()/2,image.getHeight()/2);
				matrix.postTranslate(motionNode.getX(), motionNode.getY());
				
				//アニメーションできたら指定された場所へ移動
				matrix.postTranslate(object2D.getX(), object2D.getY());
				
				canvas.drawBitmap(image, matrix, gameManager.getFont());
			}
		}
		
	}

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#2

投稿記事 by ISLe » 10年前

motionList.getMotion(motion).getMotionFrame(animationTimeLine)
この部分を逐次求める必要ないのではないでしょうか。

motionList.getMotion(motion).getMotionFrame(animationTimeLine).getMotionNode(i)
というのも効率が悪いような気がします。
イテレータを用意して効率良く順次処理すべきかと。

Matrixはまったく関係ない気がします。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#3

投稿記事 by 板野 » 10年前

わかりました、一時変数に一度代入してそこからforのループ回数とモーションノードの取得をしてみます

motionNodeList = motionList.getMotion(motion).getMotionFrame(animationTimeLine);
↓こんな感じ
・for(int i=0; i < motionNodeList.getMotionNodeSize(); i++){

でもイテレータ?でループさせるとなるとfor文では無くなりそうですね



質問中に考えていたんですが、SurfaceViewでマルチスレッドで描画している時に
共有のリソースを使うとロックが頻発してロック解除待ちのせいでフレームレートが落ちる、ということはありますか?

Android開発でちょっと画像を読み込むだけでOutOfMemory(メモリ不足)が頻発するので
一枚の画像をスタンプのように再利用できればメモリ不足が無くなるんじゃないかと、一種類の画像につき一枚のBitmapインスタンスしか持っていません。
描画時にmatrixで拡大率と回転と座標を与えてその場で変形させて望んでいる結果になってはいるのですが、
マルチスレッドの知識が曖昧でそれが原因かわかりません。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#4

投稿記事 by ISLe » 10年前

動かしてしばらくするとメモリ不足になるようであれば、どこかでリソースを複製しているのでしょう。
複製を繰り返すと参照されなくなったメモリをガベージコレクタが頻繁に回収するので、メモリ不足に関係なく全体的に処理が重くなります。

Bitmapの複製でなくてもオブジェクトの作成を繰り返すとガベージコレクタが頑張って処理を重くしてくれます。
提示されたコードからはそういうことをしていそうな雰囲気を感じます。

提示されたコードではその辺りのことは分かりませんが同じような質問がちょっと前にありました。
共有のリソースとかマルチスレッドとかは関係ないと思います。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#5

投稿記事 by 板野 » 10年前

なるほど、ありがとうございます。
行列やリソース管理ではなく、もうちょっと根本的な所を見なおしてみます。

そういえば60FPSではなく30FPS描画されて、何も処理していないまっさらな状態なのに妙に処理落ちしていたので、
Surface Viewの使い方が間違っていたのかもしれません・・・

試してみます。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#6

投稿記事 by 板野 » 10年前

FPS計測クラスを作成して重い箇所を探してみたところ、描画した途端FPSが急激に落ちることがわかりました。
計測方法参考:http:dixq.net/Android/s02_03.html

Surface View継承クラスのrunの部分

コード:

    @Override
    public void run() {
            while (thread!=null) {
            	
            	//タッチ前Frameのタッチ情報をコピー
           		notifyTouch.Mimicry(touch);
           		gameManager.SetTouch(notifyTouch);
            	
            	//次の画面を取得
            	RunScrAdmin = RunScrAdmin.GetNextScreen(gameManager);
            	RunScrAdmin.Update(gameManager);
            	Draw(getHolder());
            	
            }
    }
    
    private void Draw(SurfaceHolder holder) {
            Canvas c = holder.lockCanvas();
            
            if(c == null)return;
            c.drawColor(0x00000000, Mode.CLEAR);

            //①ResourceIDを使用してResource管理クラスからBitmapを取得する(2度目に呼び出されるときはコピーが渡される)
            //この時点ではFPSが59~60
            Bitmap menuBackGround = gameManager.getBitmap(R.drawable.menu_background);

            //②canvas.drawBitmapを実行するとFPSが40~45になる
            canvas.drawBitmap(menuBackGround,0,0, gameManager.getFont());
            
            holder.unlockCanvasAndPost(c);
    }
canvas.drawBitmapを実行するとFPSがガクンと落ちます。

①の時点でBitmapの変数を置いてるので、そのせいかもしれません。
返り値を直接渡したほうがいいですか?

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#7

投稿記事 by ISLe » 10年前

板野 さんが書きました:①の時点でBitmapの変数を置いてるので、そのせいかもしれません。
返り値を直接渡したほうがいいですか?
それはまったく関係ないでしょう。


②の行を追加した途端に重くなるということは、②の行をコメントアウトするだけで軽くなるということですか?

Bitmapを1枚描画するかしないかだけでそんなに変わるはずはないと思うのですが。

gameManager.getFont()の中身は問題ないのでしょうか。

画像リソースのプロジェクトへの格納方法や読み出し部分のコードも気になるところですが。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#8

投稿記事 by 板野 » 10年前

大きさと色とフォントを幾つか、使いわけるので予め設定しておいたPaintクラスのインスタンスをgameManager.getFont()で取得してきています。
この場合は引数がないのでデフォルトのPaintクラスのインスタンスを持ってきています(オーバーライド?というやつです

②の行のgameManager.getFont()をNULLにしても、new Paint()としてもほぼ同じFPSの低下がありました。


①の画像リソースの格納方法と呼び出しはHashMapかMapを使用して格納しています。
手元にソースコードが無いのでうろ覚えですが、こんな感じです。

コード:

Bitmap getBitmap(int id){
if(bitmapList.get(id) == NULL){
 bitmapList.put(id,BitmapFactory.decodeResource(resource, id));
}
return bitmapList.get(id);
}

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#9

投稿記事 by ISLe » 10年前

板野 さんが書きました:①の画像リソースの格納方法と呼び出しはHashMapかMapを使用して格納しています。
手元にソースコードが無いのでうろ覚えですが、こんな感じです。
それだと検索を2回行っているのが無駄ですね。
その部分は関係ないかもしれませんが、細かい無駄が積み重なるとガベージコレクションに響いてくるので注意すべきかと。

画像イメージの自動拡縮は回避されているのでしょうか。

うろ覚えですが、描画先領域を指定するメソッドのほうが速いと聞いたような気もします。


可能であれば、現象を確認できる最小構成のプロジェクト一式を提示していただけると良いのですが。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#10

投稿記事 by 板野 » 10年前

こちらがプロジェクトになります。
http://www1.axfc.net/u/3190243?key=aaa
再構成している間に、Bitmapを重ねて描画すると非常に処理が遅くなることがわかりました。
MenuクラスのDraw内で1280*720(Androidの画面サイズ)の画像を10回描画しているだけでFPSが10位以下になります。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#11

投稿記事 by 板野 » 10年前

数十秒見張ってましたがGCの頻度は高くありませんでした。
GC自体も10秒に一度で、アプリケーション名が入ってないので違うものかもしれません。
03-09 19:01:21.184: D/dalvikvm(1012): GC_FOR_ALLOC freed 2047K, 41% free 12938K/21763K, paused 29ms
03-09 19:01:31.906: D/dalvikvm(960): GC_FOR_ALLOC freed 2047K, 41% free 12977K/21763K, paused 18ms
03-09 19:01:41.189: D/dalvikvm(8985): GC_FOR_ALLOC freed 2048K, 41% free 12945K/21763K, paused 73ms
↑こんな感じです

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#12

投稿記事 by ISLe » 10年前

実機を持っていないのでエミュレータで検証してみたのですが、描画による速度低下は特にありませんでした。
環境固有の問題でしょうか。
以上を変化があるかどうか試してみてください。

それで変わらなければわたしにはもう手の打ちようがありません。
画像の変形が重要であればOpenGL(GLSurfaceView)の使用を検討してください。


余談ですが、画像サイズがあまりに大きいと思いました。
最近のスマホ事情は知らないのでわたしのほうが常識知らずかもしれませんが。

ちなみにこちらの環境では個人的に移植した80年代アーケードゲームが同程度のフレームレートで動きます。
そのゲームのapkファイルは件の画像ファイルよりもはるかに小さいです。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#13

投稿記事 by ISLe » 10年前

板野 さんが書きました:03-09 19:01:21.184: D/dalvikvm(1012): GC_FOR_ALLOC freed 2047K, 41% free 12938K/21763K, paused 29ms
03-09 19:01:31.906: D/dalvikvm(960): GC_FOR_ALLOC freed 2047K, 41% free 12977K/21763K, paused 18ms
03-09 19:01:41.189: D/dalvikvm(8985): GC_FOR_ALLOC freed 2048K, 41% free 12945K/21763K, paused 73ms
最後の数値はガベージコレクションにかかった時間です。
73msとなれば、60FPS換算で4フレーム強に渡って停止していることになります。

高フレームレートを維持したいなら、これも減らす必要があります。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#14

投稿記事 by 板野 » 10年前

余談ですが、画像サイズがあまりに大きいと思いました。
最近のスマホ事情は知らないのでわたしのほうが常識知らずかもしれませんが。
スマホの解像度が1280*720なので、1280*720の解像度の画像がいいのかな、と考えたのですが
もしかして、通常は2倍とか3倍にして使ってるのでしょうか?

最後の数値はガベージコレクションにかかった時間です。
73msとなれば、60FPS換算で4フレーム強に渡って停止していることになります。

高フレームレートを維持したいなら、これも減らす必要があります。[/quote]
ありがとうございます、試してみます。
ただ、画像を描画しているだけのアプリケーションで、これだけGCがかかっていたらゲームなんて不可能に近かったり・・・
画像の変形が重要であればOpenGL(GLSurfaceView)の使用を検討してください。
おお、こんな便利なものが・・・
SurfaceView内の処理をそのままGLSurfaceViewに移植・・・できなさそうですね

少し疑問が思い浮かんだのですがJavaのGCはnew句で生成したインスタンスのみに作用するのでしょうか?
ゲームの設計として、最初にしかnewしていなくて、触らなければ一切newされないように作っています。

ローカル変数もGCの対象になるというのであれば、

コード:

Kurasu kurasu = hoge.getKurasu();
kurasu.aaa();
kurasu.bbb();
kurasu.ccc();
というコードではなく

コード:

hoge.getKurasu().aaa();
hoge.getKurasu().bbb();
hoge.getKurasu().ccc();
としたほうがいいのでしょうか?
関数の呼び出しコストはありますが・・



・画像サイズを小さくする
・転送先矩形を指定して描画する
・GLSurfaceViewを使ってみる
を試してみます

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#15

投稿記事 by ISLe » 10年前

板野 さんが書きました:少し疑問が思い浮かんだのですがJavaのGCはnew句で生成したインスタンスのみに作用するのでしょうか?
ゲームの設計として、最初にしかnewしていなくて、触らなければ一切newされないように作っています。
自分で生成する以外にもライブラリなどが生成したオブジェクトも対象になります。
オブジェクト型変数は参照を保持しているだけなので変数の存在自体は影響ありません。
どこからも参照されなくなったオブジェクトが定期的に回収されます。

自分が書いたコードでなくてもどこかで大量にnewしているということなので、対策が必要です。

(追記)
通常のヒープメモリの回収であれば、GC_CONCURRENTと表示されます。
GC_FOR_ALLOCと表示されるのは、(正確なところは不明ですが)メモリ確保の失敗を伴う場合らしいです。
やはり画像サイズの大きさが問題である可能性が高そうです。

kiuri
記事: 20
登録日時: 10年前

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#16

投稿記事 by kiuri » 10年前

聞きかじりでしかないのですが、androidでは2^nでしか画像を高速に扱えないと聞いたことがあります。
もしそうなら今回の画像(1280*720)の場合2048*2048でメモリが確保されてしまっているのではないでしょうか?

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#17

投稿記事 by 板野 » 10年前

デバッグモードだから遅いのでは?
と考え、
android.os.Debug.waitForDebugger();
というコードをコメントアウトし、アプリケーションのアイコンから起動したところ、FPSが30ほどになりました。

うーん、こんなもんなんですかね?
blue stacksというエミュレータで起動すると60FPSになるんですが

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#18

投稿記事 by 板野 » 10年前

いや、やっぱり描画だけ重いですね
もうちょっと試してみます

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#19

投稿記事 by 板野 » 10年前

[img]uploda.cc/img/img532576853d18b.png[/img]
画像を縮小したり色々試してみましたが、ダメでした。
DrawBitmapの描画だけで処理全体の77%使ってます;

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#20

投稿記事 by ISLe » 10年前

Bitmapの描画しかしていないのなら描画部分のパーセンテージが高くて当たり前なのでは?
むしろ何もしていないはずの他の部分に2割以上掛かっていることのほうが気になりますが。

板野

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#21

投稿記事 by 板野 » 10年前

画像のURLミスりました、すいません。

あ、そうなんですか、windowsのタスクマネージャみたいにCPU全体のパーセンテージかと思ってました。
よく見たらTimeって書いてありますね・・・

また、2割以上といっても、CPUが10%とか食ってる処理は自分が作ったパッケージ名ではないですし、自分が作ったパッケージ名の処理は2~3%です。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: AndroidのSurfaceViewでのmatrix処理が非常に重い

#22

投稿記事 by ISLe » 10年前

遅いのが自分のせいではないということを証明できれば良いということなら、現状を受け入れるだけで済みます。
darwBitmapが遅い端末もあるでしょう。
このプログラムが高速に動く端末なりエミュレータなりを使いましょう。

いろいろ試されたそうですが、こちらの提案がどのように実験されてどのような結果になったのか、GC_FOR_ALLOCは解消したのか、具体的なところはさっぱり分かりません。
こちらから見たら何もなされていないのと同じですから新たに申し上げることは特にありません。

darwBitmapを単発で呼び出して遅いのであればJNIを使っても遅いはずです。
Google Playで公開されているゲームアプリもとてもプレイできないほど遅いでしょう。
#そう言えば端末の情報がないですね。

閉鎖

“C言語何でも質問掲示板” へ戻る